Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add inline actions into workspaces table#17636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 23 commits intomainfrombq/add-base-actions
May 6, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
23 commits
Select commitHold shift + click to select a range
0ba8372
feat: add inline actions into workspaces table
BrunoQuaresmaMay 1, 2025
25d986e
Fix is retrying
BrunoQuaresmaMay 1, 2025
50d9d74
Refactor actions
BrunoQuaresmaMay 1, 2025
a20a188
Simplify permissions check
BrunoQuaresmaMay 1, 2025
6f054b9
Handle action errors
BrunoQuaresmaMay 1, 2025
fcc5a27
Refactor workspace update action
BrunoQuaresmaMay 1, 2025
10dcd68
Merge branch 'main' of https://github.com/coder/coder into bq/add-bas…
BrunoQuaresmaMay 1, 2025
766463d
Run fmt
BrunoQuaresmaMay 1, 2025
c14442c
Merge branch 'main' of https://github.com/coder/coder into bq/add-bas…
BrunoQuaresmaMay 1, 2025
281cf1a
Fix WorkspacesPage tests
BrunoQuaresmaMay 1, 2025
71c6369
Run FMT
BrunoQuaresmaMay 1, 2025
3502a22
Fix WorkspacePage tests
BrunoQuaresmaMay 1, 2025
7d2b8b3
Fix storybook failed tests
BrunoQuaresmaMay 1, 2025
61dc162
Fix one more storybook test
BrunoQuaresmaMay 1, 2025
ddcb1e9
Fixes
BrunoQuaresmaMay 1, 2025
53c4332
Adjust skeleton
BrunoQuaresmaMay 1, 2025
86337e2
Align inline actions to the right
BrunoQuaresmaMay 1, 2025
821f9d0
Merge branch 'main' into bq/add-base-actions
BrunoQuaresmaMay 2, 2025
b70e28f
Apply improvements from PR review
BrunoQuaresmaMay 6, 2025
b790963
Merge branch 'bq/add-base-actions' of https://github.com/coder/coder …
BrunoQuaresmaMay 6, 2025
0987ed1
Merge branch 'main' into bq/add-base-actions
BrunoQuaresmaMay 6, 2025
c1d3046
Fix lint
BrunoQuaresmaMay 6, 2025
f797506
Merge branch 'bq/add-base-actions' of https://github.com/coder/coder …
BrunoQuaresmaMay 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletionssite/src/api/queries/workspaces.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,13 +139,9 @@ function workspacesKey(config: WorkspacesRequest = {}) {
}

export function workspaces(config: WorkspacesRequest = {}) {
// Duplicates some of the work from workspacesKey, but that felt better than
// letting invisible properties sneak into the query logic
const { q, limit } = config;

return {
queryKey: workspacesKey(config),
queryFn: () => API.getWorkspaces({ q, limit }),
queryFn: () => API.getWorkspaces(config),
} as const satisfies QueryOptions<WorkspacesResponse>;
}

Expand DownExpand Up@@ -281,7 +277,10 @@ const updateWorkspaceBuild = async (
build.workspace_owner_name,
build.workspace_name,
);
const previousData = queryClient.getQueryData(workspaceKey) as Workspace;
const previousData = queryClient.getQueryData<Workspace>(workspaceKey);
if (!previousData) {
return;
}

// Check if the build returned is newer than the previous build that could be
// updated from web socket
Expand Down
9 changes: 8 additions & 1 deletionsite/src/components/Dialogs/Dialog.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,14 @@ export const DialogActionButtons: FC<DialogActionButtonsProps> = ({
return (
<>
{onCancel && (
<Button disabled={confirmLoading} onClick={onCancel} variant="outline">
<Button
disabled={confirmLoading}
onClick={(e) => {
e.stopPropagation();
onCancel();
}}
variant="outline"
>
{cancelText}
</Button>
)}
Expand Down
6 changes: 5 additions & 1 deletionsite/src/hooks/usePagination.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 =page <= 0 ? 0 :(page - 1) *limit;
const offset =calcOffset(page,limit);

const goToPage = (page: number) => {
searchParams.set("page", page.toString());
Expand All@@ -23,3 +23,7 @@ export const usePagination = ({
offset,
};
};

export const calcOffset = (page: number, limit: number) => {
return page <= 0 ? 0 : (page - 1) * limit;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This can be turned intoMath.max(0, limit * (page - 1))

Copy link
Member

@ParkreinerParkreinerMay 2, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Also, do we want to put this function in this file? It's being imported by a lot of files that don't care about the hook

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

That is true. I just didn't want to create a moduleutils/pagination.ts just for one function, but you have a good point. What would you suggest?

};
155 changes: 155 additions & 0 deletionssite/src/modules/workspaces/WorkspaceUpdateDialogs.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
import { MissingBuildParameters } from "api/api";
import { updateWorkspace } from "api/queries/workspaces";
import type {
TemplateVersion,
Workspace,
WorkspaceBuild,
WorkspaceBuildParameter,
} from "api/typesGenerated";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { MemoizedInlineMarkdown } from "components/Markdown/Markdown";
import { UpdateBuildParametersDialog } from "pages/WorkspacePage/UpdateBuildParametersDialog";
import { type FC, useState } from "react";
import { useMutation, useQueryClient } from "react-query";

type UseWorkspaceUpdateOptions = {
workspace: Workspace;
latestVersion: TemplateVersion | undefined;
onSuccess?: (build: WorkspaceBuild) => void;
onError?: (error: unknown) => void;
};

type UseWorkspaceUpdateResult = {
update: () => void;
isUpdating: boolean;
dialogs: {
updateConfirmation: UpdateConfirmationDialogProps;
missingBuildParameters: MissingBuildParametersDialogProps;
};
};

export const useWorkspaceUpdate = ({
workspace,
latestVersion,
onSuccess,
onError,
}: UseWorkspaceUpdateOptions): UseWorkspaceUpdateResult => {
const queryClient = useQueryClient();
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false);

const updateWorkspaceOptions = updateWorkspace(workspace, queryClient);
const updateWorkspaceMutation = useMutation({
...updateWorkspaceOptions,
onSuccess: (build: WorkspaceBuild) => {
updateWorkspaceOptions.onSuccess(build);
onSuccess?.(build);
},
onError,
});

const update = () => {
setIsConfirmingUpdate(true);
};

const confirmUpdate = (buildParameters: WorkspaceBuildParameter[] = []) => {
updateWorkspaceMutation.mutate(buildParameters);
setIsConfirmingUpdate(false);
};

return {
update,
isUpdating: updateWorkspaceMutation.isLoading,
dialogs: {
updateConfirmation: {
open: isConfirmingUpdate,
onClose: () => setIsConfirmingUpdate(false),
onConfirm: () => confirmUpdate(),
latestVersion,
},
missingBuildParameters: {
error: updateWorkspaceMutation.error,
onClose: () => {
updateWorkspaceMutation.reset();
},
onUpdate: (buildParameters: WorkspaceBuildParameter[]) => {
if (updateWorkspaceMutation.error instanceof MissingBuildParameters) {
confirmUpdate(buildParameters);
}
},
},
},
};
};

type WorkspaceUpdateDialogsProps = {
updateConfirmation: UpdateConfirmationDialogProps;
missingBuildParameters: MissingBuildParametersDialogProps;
};

export const WorkspaceUpdateDialogs: FC<WorkspaceUpdateDialogsProps> = ({
updateConfirmation,
missingBuildParameters,
}) => {
return (
<>
<UpdateConfirmationDialog {...updateConfirmation} />
<MissingBuildParametersDialog {...missingBuildParameters} />
</>
);
};

type UpdateConfirmationDialogProps = {
open: boolean;
onClose: () => void;
onConfirm: () => void;
latestVersion?: TemplateVersion;
};

const UpdateConfirmationDialog: FC<UpdateConfirmationDialogProps> = ({
latestVersion,
...dialogProps
}) => {
return (
<ConfirmDialog
{...dialogProps}
hideCancel={false}
title="Update workspace?"
confirmText="Update"
description={
<div className="flex flex-col gap-2">
<p>
Updating your workspace will start the workspace on the latest
template version. This can{" "}
<strong>delete non-persistent data</strong>.
</p>
{latestVersion?.message && (
<MemoizedInlineMarkdown allowedElements={["ol", "ul", "li"]}>
{latestVersion.message}
</MemoizedInlineMarkdown>
)}
</div>
}
/>
);
};

type MissingBuildParametersDialogProps = {
error: unknown;
onClose: () => void;
onUpdate: (buildParameters: WorkspaceBuildParameter[]) => void;
};

const MissingBuildParametersDialog: FC<MissingBuildParametersDialogProps> = ({
error,
...dialogProps
}) => {
return (
<UpdateBuildParametersDialog
missedParameters={
error instanceof MissingBuildParameters ? error.parameters : []
}
open={error instanceof MissingBuildParameters}
{...dialogProps}
/>
);
};
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,6 +34,11 @@ const actionTypes = [

export type ActionType = (typeof actionTypes)[number];

type ActionPermissions = {
canDebug: boolean;
isOwner: boolean;
};

type WorkspaceAbilities = {
actions: readonly ActionType[];
canCancel: boolean;
Expand All@@ -42,8 +47,11 @@ type WorkspaceAbilities = {

export const abilitiesByWorkspaceStatus = (
workspace: Workspace,
canDebug: boolean,
permissions: ActionPermissions,
): WorkspaceAbilities => {
const hasPermissionToCancel =
workspace.template_allow_user_cancel_workspace_jobs || permissions.isOwner;

if (workspace.dormant_at) {
return {
actions: ["activate"],
Expand All@@ -58,7 +66,7 @@ export const abilitiesByWorkspaceStatus = (
case "starting": {
return {
actions: ["starting"],
canCancel:true,
canCancel:hasPermissionToCancel,
canAcceptJobs: false,
};
}
Expand All@@ -83,7 +91,7 @@ export const abilitiesByWorkspaceStatus = (
case "stopping": {
return {
actions: ["stopping"],
canCancel:true,
canCancel:hasPermissionToCancel,
canAcceptJobs: false,
};
}
Expand DownExpand Up@@ -115,7 +123,7 @@ export const abilitiesByWorkspaceStatus = (
case "failed": {
const actions: ActionType[] = ["retry"];

if (canDebug) {
if (permissions.canDebug) {
actions.push("debug");
}

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
import type { Template, Workspace } from "api/typesGenerated";
import { compareAsc } from "date-fns";
import { calcOffset } from "hooks/usePagination";
import { useWorkspacesData } from "pages/WorkspacesPage/data";
import type { TemplateScheduleFormValues } from "./formHelpers";

Expand All@@ -9,9 +10,9 @@ export const useWorkspacesToGoDormant = (
fromDate: Date,
) => {
const { data } = useWorkspacesData({
page: 0,
offset: calcOffset(0, 0),
limit: 0,
query: `template:${template.name}`,
q: `template:${template.name}`,
});

return data?.workspaces?.filter((workspace: Workspace) => {
Expand DownExpand Up@@ -40,9 +41,9 @@ export const useWorkspacesToBeDeleted = (
fromDate: Date,
) => {
const { data } = useWorkspacesData({
page: 0,
offset: calcOffset(0, 0),
limit: 0,
query: `template:${template.name} dormant:true`,
q: `template:${template.name} dormant:true`,
});
return data?.workspaces?.filter((workspace: Workspace) => {
if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) {
Expand Down
4 changes: 3 additions & 1 deletionsite/src/pages/WorkspacePage/Workspace.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import type { ProvisionerJobLog } from "api/typesGenerated";
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
import * as Mocks from "testHelpers/entities";
import { withDashboardProvider } from "testHelpers/storybook";
import {withAuthProvider,withDashboardProvider } from "testHelpers/storybook";
import { Workspace } from "./Workspace";
import type { WorkspacePermissions } from "./permissions";

Expand DownExpand Up@@ -40,8 +40,10 @@ const meta: Meta<typeof Workspace> = {
data: Mocks.MockListeningPortsResponse,
},
],
user: Mocks.MockUser,
},
decorators: [
withAuthProvider,
withDashboardProvider,
(Story) => (
<ProxyContext.Provider
Expand Down
3 changes: 0 additions & 3 deletionssite/src/pages/WorkspacePage/Workspace.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,7 +53,6 @@ export interface WorkspaceProps {
buildLogs?: TypesGen.ProvisionerJobLog[];
latestVersion?: TypesGen.TemplateVersion;
permissions: WorkspacePermissions;
isOwner: boolean;
timings?: TypesGen.WorkspaceBuildTimings;
}

Expand DownExpand Up@@ -86,7 +85,6 @@ export const Workspace: FC<WorkspaceProps> = ({
buildLogs,
latestVersion,
permissions,
isOwner,
timings,
}) => {
const navigate = useNavigate();
Expand DownExpand Up@@ -161,7 +159,6 @@ export const Workspace: FC<WorkspaceProps> = ({
isUpdating={isUpdating}
isRestarting={isRestarting}
canUpdateWorkspace={permissions.updateWorkspace}
isOwner={isOwner}
template={template}
permissions={permissions}
latestVersion={latestVersion}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import { expect, userEvent, within } from "@storybook/test";
import { agentLogsKey, buildLogsKey } from "api/queries/workspaces";
import * as Mocks from "testHelpers/entities";
import {
withAuthProvider,
withDashboardProvider,
withDesktopViewport,
} from "testHelpers/storybook";
Expand All@@ -14,7 +15,10 @@ const meta: Meta<typeof WorkspaceActions> = {
args: {
isUpdating: false,
},
decorators: [withDashboardProvider, withDesktopViewport],
decorators: [withDashboardProvider, withDesktopViewport, withAuthProvider],
parameters: {
user: Mocks.MockUser,
},
};

export default meta;
Expand DownExpand Up@@ -163,14 +167,15 @@ export const CancelShownForOwner: Story = {
...Mocks.MockStartingWorkspace,
template_allow_user_cancel_workspace_jobs: false,
},
isOwner: true,
},
};

export const CancelShownForUser: Story = {
args: {
workspace: Mocks.MockStartingWorkspace,
isOwner: false,
},
parameters: {
user: Mocks.MockUser2,
},
};

Expand All@@ -180,7 +185,9 @@ export const CancelHiddenForUser: Story = {
...Mocks.MockStartingWorkspace,
template_allow_user_cancel_workspace_jobs: false,
},
isOwner: false,
},
parameters: {
user: Mocks.MockUser2,
},
};

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp