mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): allow updating workspace in TaskPage (#21316)
Relates to #20925 This PR modifies TaskPage to update an outdated workspace instead of starting it. Before, starting an outdated workspace where the template required the active version would fail with the error "cannot use non-active version: rbac: forbidden". For the case of a dormant workspace, I deemed it safe enough to simply unset dormancy on an attempted start (ref: https://github.com/coder/coder/pull/21306). However, automatically updating a workspace is a more risky option, so I instead elected to give the user the option of updating their workspace using the existing tooltip. **Note:** I made a change to the `WorkspaceOutdatedTooltip` components to allow it to have children so that the tooltip could trigger over a wider element instead of just the info icon. ``` <🤖 AI Disclaimer>I got some help from Gemini 3 Flash in "Ask" mode.</🤖 AI Disclaimer> ```
This commit is contained in:
@@ -13,10 +13,11 @@ import {
|
||||
HelpTooltipLinksGroup,
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
HelpTooltipTrigger,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { InfoIcon, RotateCcwIcon } from "lucide-react";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import { type FC, useState } from "react";
|
||||
import { type FC, type ReactNode, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
useWorkspaceUpdate,
|
||||
@@ -25,20 +26,31 @@ import {
|
||||
|
||||
interface WorkspaceOutdatedTooltipProps {
|
||||
workspace: Workspace;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const WorkspaceOutdatedTooltip: FC<WorkspaceOutdatedTooltipProps> = (
|
||||
props,
|
||||
) => {
|
||||
export const WorkspaceOutdatedTooltip: FC<WorkspaceOutdatedTooltipProps> = ({
|
||||
workspace,
|
||||
children,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<HelpTooltip open={isOpen} onOpenChange={setIsOpen}>
|
||||
<HelpTooltipIconTrigger size="small" hoverEffect={false}>
|
||||
<InfoIcon css={styles.icon} />
|
||||
<span className="sr-only">Outdated info</span>
|
||||
</HelpTooltipIconTrigger>
|
||||
<WorkspaceOutdatedTooltipContent isOpen={isOpen} {...props} />
|
||||
{children ? (
|
||||
<HelpTooltipTrigger asChild>
|
||||
<span className="flex items-center gap-1.5 cursor-help">
|
||||
<InfoIcon css={styles.icon} size={14} />
|
||||
<span>{children}</span>
|
||||
</span>
|
||||
</HelpTooltipTrigger>
|
||||
) : (
|
||||
<HelpTooltipIconTrigger size="small" hoverEffect={false}>
|
||||
<InfoIcon css={styles.icon} />
|
||||
<span className="sr-only">Outdated info</span>
|
||||
</HelpTooltipIconTrigger>
|
||||
)}
|
||||
<WorkspaceOutdatedTooltipContent isOpen={isOpen} workspace={workspace} />
|
||||
</HelpTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "testHelpers/entities";
|
||||
import {
|
||||
withAuthProvider,
|
||||
withDashboardProvider,
|
||||
withGlobalSnackbar,
|
||||
withProxyProvider,
|
||||
withWebSocket,
|
||||
@@ -65,7 +66,7 @@ const MockVSCodeApp: WorkspaceApp = {
|
||||
const meta: Meta<typeof TaskPage> = {
|
||||
title: "pages/TaskPage",
|
||||
component: TaskPage,
|
||||
decorators: [withProxyProvider(), withAuthProvider],
|
||||
decorators: [withProxyProvider(), withAuthProvider, withDashboardProvider],
|
||||
beforeEach: () => {
|
||||
spyOn(API, "getTasks").mockResolvedValue(MockTasks);
|
||||
},
|
||||
@@ -402,6 +403,50 @@ export const MainAppHealthy: Story = mainAppHealthStory("healthy");
|
||||
export const MainAppInitializing: Story = mainAppHealthStory("initializing");
|
||||
export const MainAppUnhealthy: Story = mainAppHealthStory("unhealthy");
|
||||
|
||||
export const OutdatedWorkspace: Story = {
|
||||
// Given: an 'outdated' workspace (that is, the latest build does not use template's active version)
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["tasks", { owner: MockTask.owner_name }],
|
||||
data: [MockTask],
|
||||
},
|
||||
{
|
||||
key: ["tasks", MockTask.owner_name, MockTask.id],
|
||||
data: MockTask,
|
||||
},
|
||||
{
|
||||
key: [
|
||||
"workspace",
|
||||
MockTask.owner_name,
|
||||
MockTask.workspace_name,
|
||||
"settings",
|
||||
],
|
||||
data: {
|
||||
...MockStoppedWorkspace,
|
||||
outdated: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: [
|
||||
"workspaceBuilds",
|
||||
MockStoppedWorkspace.latest_build.id,
|
||||
"parameters",
|
||||
],
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Then: a tooltip should be displayed prompting the user to update the workspace.
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const outdatedTooltip = await canvas.findByTestId(
|
||||
"workspace-outdated-tooltip",
|
||||
);
|
||||
expect(outdatedTooltip).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
export const Active: Story = {
|
||||
decorators: [withProxyProvider()],
|
||||
beforeEach: () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { getAllAppsWithAgent } from "modules/tasks/apps";
|
||||
import { TasksSidebar } from "modules/tasks/TasksSidebar/TasksSidebar";
|
||||
import { WorkspaceErrorDialog } from "modules/workspaces/ErrorDialog/WorkspaceErrorDialog";
|
||||
import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs";
|
||||
import { WorkspaceOutdatedTooltip } from "modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip";
|
||||
import {
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
@@ -275,9 +276,20 @@ const WorkspaceNotRunning: FC<WorkspaceNotRunningProps> = ({
|
||||
<span className="text-content-secondary text-sm">
|
||||
Apps and previous statuses are not available
|
||||
</span>
|
||||
{workspace.outdated && (
|
||||
<div
|
||||
data-testid="workspace-outdated-tooltip"
|
||||
className="flex items-center gap-1.5 mt-1 text-content-secondary text-sm"
|
||||
>
|
||||
<WorkspaceOutdatedTooltip workspace={workspace}>
|
||||
You can update your task workspace to a newer version
|
||||
</WorkspaceOutdatedTooltip>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row mt-4 gap-4">
|
||||
<Button
|
||||
size="sm"
|
||||
data-testid="task-start-workspace"
|
||||
disabled={isWaitingForStart}
|
||||
onClick={() => {
|
||||
mutateStartWorkspace.mutate({
|
||||
|
||||
Reference in New Issue
Block a user