Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
feat: add View Source button to experimental component and refactor p…
…ermissions

Addresses review feedback:
- Added View Source button to CreateWorkspacePageViewExperimental component
- Refactored createWorkspaceChecks helper to include template update permission
- Updated both regular and experimental pages to use the new permissions helper
- Added story for experimental component with View Source button
- Removed unnecessary WithoutViewSourceButton story (default behavior)

The View Source button now appears in both the regular and experimental
workspace creation pages for template administrators.

Co-authored-by: matifali <10648092+matifali@users.noreply.github.com>
  • Loading branch information
blink-so[bot] and matifali committed Jul 23, 2025
commit aca021171eb52eb429e0b201eebf1afc95e61458
14 changes: 4 additions & 10 deletions site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,10 @@ const CreateWorkspacePage: FC = () => {
});
const permissionsQuery = useQuery({
...checkAuthorization({
checks: {
...createWorkspaceChecks(templateQuery.data?.organization_id ?? ""),
canUpdateTemplate: {
object: {
resource_type: "template",
resource_id: templateQuery.data?.id ?? "",
},
action: "update",
},
},
checks: createWorkspaceChecks(
templateQuery.data?.organization_id ?? "",
templateQuery.data?.id,
),
}),
enabled: !!templateQuery.data,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ const CreateWorkspacePageExperimental: FC = () => {
});
const permissionsQuery = useQuery({
...checkAuthorization({
checks: createWorkspaceChecks(templateQuery.data?.organization_id ?? ""),
checks: createWorkspaceChecks(
templateQuery.data?.organization_id ?? "",
templateQuery.data?.id,
),
}),
enabled: !!templateQuery.data,
});
Expand Down Expand Up @@ -292,6 +295,7 @@ const CreateWorkspacePageExperimental: FC = () => {
owner={owner}
setOwner={setOwner}
autofillParameters={autofillParameters}
canUpdateTemplate={permissionsQuery.data?.canUpdateTemplate}
error={
wsError ||
createWorkspaceMutation.error ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const meta: Meta<typeof CreateWorkspacePageView> = {
mode: "form",
permissions: {
createWorkspaceForAny: true,
canUpdateTemplate: false,
},
onCancel: action("onCancel"),
},
Expand Down Expand Up @@ -402,23 +403,3 @@ export const WithViewSourceButton: Story = {
},
},
};

export const WithoutViewSourceButton: Story = {
args: {
canUpdateTemplate: false,
versionId: "template-version-123",
template: {
...MockTemplate,
organization_name: "default",
name: "docker-template",
},
},
parameters: {
docs: {
description: {
story:
"This story shows the workspace creation page for users without template update permissions. The View Source button is hidden for these users.",
},
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const meta: Meta<typeof CreateWorkspacePageViewExperimental> = {
parameters: [],
permissions: {
createWorkspaceForAny: true,
canUpdateTemplate: false,
},
presets: [],
sendMessage: () => {},
Expand All @@ -38,3 +39,23 @@ export const WebsocketError: Story = {
),
},
};

export const WithViewSourceButton: Story = {
args: {
canUpdateTemplate: true,
versionId: "template-version-123",
template: {
...MockTemplate,
organization_name: "default",
name: "docker-template",
},
},
parameters: {
docs: {
description: {
story:
"This story shows the View Source button that appears for template administrators in the experimental workspace creation page. The button allows quick navigation to the template editor.",
},
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
import { type FormikContextType, useFormik } from "formik";
import type { ExternalAuthPollingState } from "hooks/useExternalAuth";
import { ArrowLeft, CircleHelp } from "lucide-react";
import { ArrowLeft, CircleHelp, ExternalLinkIcon } from "lucide-react";
import { useSyncFormParameters } from "modules/hooks/useSyncFormParameters";
import {
Diagnostics,
Expand All @@ -44,6 +44,7 @@ import {
useRef,
useState,
} from "react";
import { Link as RouterLink } from "react-router-dom";
import { docs } from "utils/docs";
import { nameValidator } from "utils/formUtils";
import type { AutofillBuildParameter } from "utils/richParameters";
Expand All @@ -54,6 +55,7 @@ import type { CreateWorkspacePermissions } from "./permissions";

interface CreateWorkspacePageViewExperimentalProps {
autofillParameters: AutofillBuildParameter[];
canUpdateTemplate?: boolean;
creatingWorkspace: boolean;
defaultName?: string | null;
defaultOwner: TypesGen.User;
Expand Down Expand Up @@ -85,6 +87,7 @@ export const CreateWorkspacePageViewExperimental: FC<
CreateWorkspacePageViewExperimentalProps
> = ({
autofillParameters,
canUpdateTemplate,
creatingWorkspace,
defaultName,
defaultOwner,
Expand Down Expand Up @@ -379,6 +382,16 @@ export const CreateWorkspacePageViewExperimental: FC<
</Badge>
)}
</span>
{canUpdateTemplate && (
<Button asChild size="sm" variant="outline">
<RouterLink
to={`/templates/${template.organization_name}/${template.name}/versions/${versionId}/edit`}
>
<ExternalLinkIcon />
View source
</RouterLink>
</Button>
)}
</div>
<span className="flex flex-row items-center gap-2">
<h1 className="text-3xl font-semibold m-0">New workspace</h1>
Expand Down
18 changes: 15 additions & 3 deletions site/src/pages/CreateWorkspacePage/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
export const createWorkspaceChecks = (organizationId: string) =>
export const createWorkspaceChecks = (
organizationId: string,
templateId?: string,
) =>
({
createWorkspaceForAny: {
object: {
resource_type: "workspace",
resource_type: "workspace" as const,
organization_id: organizationId,
owner_id: "*",
},
action: "create",
action: "create" as const,
},
...(templateId && {
canUpdateTemplate: {
object: {
resource_type: "template" as const,
resource_id: templateId,
},
action: "update" as const,
},
}),
}) as const;

export type CreateWorkspacePermissions = Record<
Expand Down
Loading