diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index 8995222027ed0..e6b23b8a4dd94 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -19,7 +19,7 @@ const badgeVariants = cva( warning: "border border-solid border-border-warning bg-surface-orange text-content-warning shadow", destructive: - "border border-solid border-border-destructive bg-surface-red text-content-highlight-red shadow", + "border border-solid border-border-destructive bg-surface-red text-highlight-red shadow", }, size: { xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", diff --git a/site/src/hooks/usePagination.ts b/site/src/hooks/usePagination.ts index 2ab409e85c9d8..72ea70868fb30 100644 --- a/site/src/hooks/usePagination.ts +++ b/site/src/hooks/usePagination.ts @@ -9,7 +9,7 @@ export const usePagination = ({ const [searchParams, setSearchParams] = searchParamsResult; const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1; const limit = DEFAULT_RECORDS_PER_PAGE; - const offset = calcOffset(page, limit); + const offset = page <= 0 ? 0 : (page - 1) * limit; const goToPage = (page: number) => { searchParams.set("page", page.toString()); @@ -23,7 +23,3 @@ export const usePagination = ({ offset, }; }; - -export const calcOffset = (page: number, limit: number) => { - return page <= 0 ? 0 : (page - 1) * limit; -}; diff --git a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx index bb0c93e74c8c6..843f8131b793f 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx @@ -1,7 +1,10 @@ -import { action } from "@storybook/addon-actions"; import type { Meta, StoryObj } from "@storybook/react"; import { expect, userEvent, waitFor, within } from "@storybook/test"; -import { MockTemplate, MockTemplateVersion } from "testHelpers/entities"; +import { + MockTemplate, + MockTemplateVersion, + MockWorkspace, +} from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import { WorkspaceOutdatedTooltip } from "./WorkspaceOutdatedTooltip"; @@ -18,9 +21,11 @@ const meta: Meta = { ], }, args: { - onUpdateVersion: action("onUpdateVersion"), - templateName: MockTemplate.display_name, - latestVersionId: MockTemplateVersion.id, + workspace: { + ...MockWorkspace, + template_name: MockTemplate.display_name, + template_active_version_id: MockTemplateVersion.id, + }, }, }; @@ -29,14 +34,12 @@ type Story = StoryObj; const Example: Story = { play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); await step("activate hover trigger", async () => { - await userEvent.hover(screen.getByRole("button")); + await userEvent.hover(body.getByRole("button")); await waitFor(() => - expect( - screen.getByText(MockTemplateVersion.message), - ).toBeInTheDocument(), + expect(body.getByText(MockTemplateVersion.message)).toBeInTheDocument(), ); }); }, diff --git a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx index ada4c63d9a0bb..9615560840c59 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx @@ -3,7 +3,10 @@ import InfoIcon from "@mui/icons-material/InfoOutlined"; import RefreshIcon from "@mui/icons-material/Refresh"; import Link from "@mui/material/Link"; import Skeleton from "@mui/material/Skeleton"; +import { getErrorDetail, getErrorMessage } from "api/errors"; import { templateVersion } from "api/queries/templates"; +import type { Workspace } from "api/typesGenerated"; +import { displayError } from "components/GlobalSnackbar/utils"; import { HelpTooltip, HelpTooltipAction, @@ -17,102 +20,99 @@ import { usePopover } from "components/deprecated/Popover/Popover"; import { linkToTemplate, useLinks } from "modules/navigation"; import type { FC } from "react"; import { useQuery } from "react-query"; - -const Language = { - outdatedLabel: "Outdated", - versionTooltipText: - "This workspace version is outdated and a newer version is available.", - updateVersionLabel: "Update", -}; +import { + WorkspaceUpdateDialogs, + useWorkspaceUpdate, +} from "../WorkspaceUpdateDialogs"; interface TooltipProps { - organizationName: string; - templateName: string; - latestVersionId: string; - onUpdateVersion: () => void; - ariaLabel?: string; + workspace: Workspace; } export const WorkspaceOutdatedTooltip: FC = (props) => { return ( - + + Outdated info - ); }; -const WorkspaceOutdatedTooltipContent: FC = ({ - organizationName, - templateName, - latestVersionId, - onUpdateVersion, - ariaLabel, -}) => { +const WorkspaceOutdatedTooltipContent: FC = ({ workspace }) => { const getLink = useLinks(); const theme = useTheme(); const popover = usePopover(); const { data: activeVersion } = useQuery({ - ...templateVersion(latestVersionId), + ...templateVersion(workspace.template_active_version_id), enabled: popover.open, }); + const updateWorkspace = useWorkspaceUpdate({ + workspace, + latestVersion: activeVersion, + onError: (error) => { + displayError( + getErrorMessage(error, "Error updating workspace"), + getErrorDetail(error), + ); + }, + }); const versionLink = `${getLink( - linkToTemplate(organizationName, templateName), + linkToTemplate(workspace.organization_name, workspace.template_name), )}`; return ( - - {Language.outdatedLabel} - {Language.versionTooltipText} + <> + + Outdated + + This workspace version is outdated and a newer version is available. + -
-
-
New version
-
- {activeVersion ? ( - - {activeVersion.name} - - ) : ( - - )} +
+
+
New version
+
+ {activeVersion ? ( + + {activeVersion.name} + + ) : ( + + )} +
-
-
-
Message
-
- {activeVersion ? ( - activeVersion.message || "No message" - ) : ( - - )} +
+
Message
+
+ {activeVersion ? ( + activeVersion.message || "No message" + ) : ( + + )} +
-
- - - {Language.updateVersionLabel} - - - + + + Update + + + + + ); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts index 5a974d5d8fe31..d55f0b6c54fff 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts @@ -1,7 +1,7 @@ +import { workspaces } from "api/queries/workspaces"; import type { Template, Workspace } from "api/typesGenerated"; import { compareAsc } from "date-fns"; -import { calcOffset } from "hooks/usePagination"; -import { useWorkspacesData } from "pages/WorkspacesPage/data"; +import { useQuery } from "react-query"; import type { TemplateScheduleFormValues } from "./formHelpers"; export const useWorkspacesToGoDormant = ( @@ -9,11 +9,11 @@ export const useWorkspacesToGoDormant = ( formValues: TemplateScheduleFormValues, fromDate: Date, ) => { - const { data } = useWorkspacesData({ - offset: calcOffset(0, 0), - limit: 0, - q: `template:${template.name}`, - }); + const { data } = useQuery( + workspaces({ + q: `template:${template.name}`, + }), + ); return data?.workspaces?.filter((workspace: Workspace) => { if (!formValues.time_til_dormant_ms) { @@ -40,11 +40,12 @@ export const useWorkspacesToBeDeleted = ( formValues: TemplateScheduleFormValues, fromDate: Date, ) => { - const { data } = useWorkspacesData({ - offset: calcOffset(0, 0), - limit: 0, - q: `template:${template.name} dormant:true`, - }); + const { data } = useQuery( + workspaces({ + q: `template:${template.name} dormant:true`, + }), + ); + return data?.workspaces?.filter((workspace: Workspace) => { if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) { return false; diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 551c554fd5ee3..d0439be0f0462 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -1,6 +1,7 @@ import { getErrorDetail, getErrorMessage } from "api/errors"; import { workspacePermissionsByOrganization } from "api/queries/organizations"; import { templates } from "api/queries/templates"; +import { workspaces } from "api/queries/workspaces"; import type { Workspace } from "api/typesGenerated"; import { useFilter } from "components/Filter/Filter"; import { useUserFilterMenu } from "components/Filter/UserFilter"; @@ -19,7 +20,6 @@ import { BatchDeleteConfirmation } from "./BatchDeleteConfirmation"; import { BatchUpdateConfirmation } from "./BatchUpdateConfirmation"; import { WorkspacesPageView } from "./WorkspacesPageView"; import { useBatchActions } from "./batchActions"; -import { useWorkspaceUpdate, useWorkspacesData } from "./data"; import { useStatusFilterMenu, useTemplateFilterMenu } from "./filter/menus"; function useSafeSearchParams() { @@ -45,9 +45,7 @@ const WorkspacesPage: FC = () => { const pagination = usePagination({ searchParamsResult }); const { permissions, user: me } = useAuthenticated(); const { entitlements } = useDashboard(); - const templatesQuery = useQuery(templates()); - const workspacePermissionsQuery = useQuery( workspacePermissionsByOrganization( templatesQuery.data?.map((template) => template.organization_id), @@ -73,12 +71,17 @@ const WorkspacesPage: FC = () => { onFilterChange: () => pagination.goToPage(1), }); - const { data, error, queryKey, refetch } = useWorkspacesData({ + const workspacesQueryOptions = workspaces({ ...pagination, q: filterProps.filter.query, }); + const { data, error, refetch } = useQuery({ + ...workspacesQueryOptions, + refetchInterval: (_, query) => { + return query.state.error ? false : 5_000; + }, + }); - const updateWorkspace = useWorkspaceUpdate(queryKey); const [checkedWorkspaces, setCheckedWorkspaces] = useState< readonly Workspace[] >([]); @@ -123,9 +126,6 @@ const WorkspacesPage: FC = () => { limit={pagination.limit} onPageChange={pagination.goToPage} filterProps={filterProps} - onUpdateWorkspace={(workspace) => { - updateWorkspace.mutate(workspace); - }} isRunningBatchAction={batchActions.isLoading} onDeleteAll={() => setConfirmingBatchAction("delete")} onUpdateAll={() => setConfirmingBatchAction("update")} @@ -133,7 +133,7 @@ const WorkspacesPage: FC = () => { onStopAll={() => batchActions.stopAll(checkedWorkspaces)} onActionSuccess={async () => { await queryClient.invalidateQueries({ - queryKey, + queryKey: workspacesQueryOptions.queryKey, }); }} onActionError={(error) => { diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 6db41fef8fa6c..6633f884e1263 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -53,7 +53,6 @@ export interface WorkspacesPageViewProps { page: number; limit: number; onPageChange: (page: number) => void; - onUpdateWorkspace: (workspace: Workspace) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void; isRunningBatchAction: boolean; onDeleteAll: () => void; @@ -76,7 +75,6 @@ export const WorkspacesPageView: FC = ({ count, filterProps, onPageChange, - onUpdateWorkspace, page, checkedWorkspaces, onCheckChange, @@ -223,7 +221,6 @@ export const WorkspacesPageView: FC = ({ canCreateTemplate={canCreateTemplate} workspaces={workspaces} isUsingFilter={filterProps.filter.used} - onUpdateWorkspace={onUpdateWorkspace} checkedWorkspaces={checkedWorkspaces} onCheckChange={onCheckChange} canCheckWorkspaces={canCheckWorkspaces} diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 92dcee60dec96..b4f1c98b27261 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -97,7 +97,6 @@ export interface WorkspacesTableProps { checkedWorkspaces: readonly Workspace[]; error?: unknown; isUsingFilter: boolean; - onUpdateWorkspace: (workspace: Workspace) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void; canCheckWorkspaces: boolean; templates?: Template[]; @@ -110,7 +109,6 @@ export const WorkspacesTable: FC = ({ workspaces, checkedWorkspaces, isUsingFilter, - onUpdateWorkspace, onCheckChange, canCheckWorkspaces, templates, @@ -243,16 +241,7 @@ export const WorkspacesTable: FC = ({ {workspace.name} {workspace.favorite && } {workspace.outdated && ( - { - onUpdateWorkspace(workspace); - }} - /> + )} } diff --git a/site/src/pages/WorkspacesPage/data.ts b/site/src/pages/WorkspacesPage/data.ts deleted file mode 100644 index 764ea218aa96c..0000000000000 --- a/site/src/pages/WorkspacesPage/data.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { API } from "api/api"; -import { getErrorMessage } from "api/errors"; -import { workspaces } from "api/queries/workspaces"; -import type { - Workspace, - WorkspaceBuild, - WorkspacesRequest, - WorkspacesResponse, -} from "api/typesGenerated"; -import { displayError } from "components/GlobalSnackbar/utils"; -import { useState } from "react"; -import { - type QueryKey, - useMutation, - useQuery, - useQueryClient, -} from "react-query"; - -export const useWorkspacesData = (req: WorkspacesRequest) => { - const [shouldRefetch, setShouldRefetch] = useState(true); - const workspacesQueryOptions = workspaces(req); - const result = useQuery({ - ...workspacesQueryOptions, - onSuccess: () => { - setShouldRefetch(true); - }, - onError: () => { - setShouldRefetch(false); - }, - refetchInterval: shouldRefetch ? 5_000 : undefined, - }); - - return { - ...result, - queryKey: workspacesQueryOptions.queryKey, - }; -}; - -export const useWorkspaceUpdate = (queryKey: QueryKey) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: API.updateWorkspaceVersion, - onMutate: async (workspace) => { - await queryClient.cancelQueries({ queryKey }); - queryClient.setQueryData(queryKey, (oldResponse) => { - if (oldResponse) { - return assignPendingStatus(oldResponse, workspace); - } - }); - }, - onSuccess: (workspaceBuild) => { - queryClient.setQueryData(queryKey, (oldResponse) => { - if (oldResponse) { - return assignLatestBuild(oldResponse, workspaceBuild); - } - }); - }, - onError: (error) => { - const message = getErrorMessage( - error, - "Error updating workspace version", - ); - displayError(message); - }, - }); -}; - -const assignLatestBuild = ( - oldResponse: WorkspacesResponse, - build: WorkspaceBuild, -): WorkspacesResponse => { - return { - ...oldResponse, - workspaces: oldResponse.workspaces.map((workspace) => { - if (workspace.id === build.workspace_id) { - return { - ...workspace, - latest_build: build, - }; - } - - return workspace; - }), - }; -}; - -const assignPendingStatus = ( - oldResponse: WorkspacesResponse, - workspace: Workspace, -): WorkspacesResponse => { - return { - ...oldResponse, - workspaces: oldResponse.workspaces.map((workspaceItem) => { - if (workspaceItem.id === workspace.id) { - return { - ...workspace, - latest_build: { - ...workspace.latest_build, - status: "pending", - job: { - ...workspace.latest_build.job, - status: "pending", - }, - }, - } as Workspace; - } - - return workspaceItem; - }), - }; -}; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy