feat: add View Source button for template administrators in workspace creation (#18951)

This commit is contained in:
blink-so[bot]
2025-07-23 11:16:53 -06:00
committed by GitHub
parent bb83071b5f
commit 28789d7204
7 changed files with 100 additions and 9 deletions
@@ -65,7 +65,10 @@ const CreateWorkspacePage: FC = () => {
});
const permissionsQuery = useQuery({
...checkAuthorization({
checks: createWorkspaceChecks(templateQuery.data?.organization_id ?? ""),
checks: createWorkspaceChecks(
templateQuery.data?.organization_id ?? "",
templateQuery.data?.id,
),
}),
enabled: !!templateQuery.data,
});
@@ -208,6 +211,7 @@ const CreateWorkspacePage: FC = () => {
startPollingExternalAuth={startPollingExternalAuth}
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
permissions={permissionsQuery.data as CreateWorkspacePermissions}
canUpdateTemplate={permissionsQuery.data?.canUpdateTemplate}
parameters={realizedParameters as TemplateVersionParameter[]}
presets={templateVersionPresetsQuery.data ?? []}
creatingWorkspace={createWorkspaceMutation.isPending}
@@ -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,
});
@@ -292,6 +295,7 @@ const CreateWorkspacePageExperimental: FC = () => {
owner={owner}
setOwner={setOwner}
autofillParameters={autofillParameters}
canUpdateTemplate={permissionsQuery.data?.canUpdateTemplate}
error={
wsError ||
createWorkspaceMutation.error ||
@@ -28,6 +28,7 @@ const meta: Meta<typeof CreateWorkspacePageView> = {
mode: "form",
permissions: {
createWorkspaceForAny: true,
canUpdateTemplate: false,
},
onCancel: action("onCancel"),
},
@@ -382,3 +383,23 @@ export const ExternalAuthAllConnected: 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. The button allows quick navigation to the template editor from the workspace creation page.",
},
},
},
};
@@ -27,8 +27,10 @@ import { Switch } from "components/Switch/Switch";
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
import { type FormikContextType, useFormik } from "formik";
import type { ExternalAuthPollingState } from "hooks/useExternalAuth";
import { ExternalLinkIcon } from "lucide-react";
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import {
getFormHelpers,
nameValidator,
@@ -67,6 +69,7 @@ interface CreateWorkspacePageViewProps {
presets: TypesGen.Preset[];
permissions: CreateWorkspacePermissions;
creatingWorkspace: boolean;
canUpdateTemplate?: boolean;
onCancel: () => void;
onSubmit: (
req: TypesGen.CreateWorkspaceRequest,
@@ -92,6 +95,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
presets = [],
permissions,
creatingWorkspace,
canUpdateTemplate,
onSubmit,
onCancel,
}) => {
@@ -218,9 +222,21 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
<Margins size="medium">
<PageHeader
actions={
<Button size="sm" variant="outline" onClick={onCancel}>
Cancel
</Button>
<Stack direction="row" spacing={2}>
{canUpdateTemplate && (
<Button asChild size="sm" variant="outline">
<Link
to={`/templates/${template.organization_name}/${template.name}/versions/${versionId}/edit`}
>
<ExternalLinkIcon />
View source
</Link>
</Button>
)}
<Button size="sm" variant="outline" onClick={onCancel}>
Cancel
</Button>
</Stack>
}
>
<Stack direction="row">
@@ -20,6 +20,7 @@ const meta: Meta<typeof CreateWorkspacePageViewExperimental> = {
parameters: [],
permissions: {
createWorkspaceForAny: true,
canUpdateTemplate: false,
},
presets: [],
sendMessage: () => {},
@@ -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.",
},
},
},
};
@@ -26,7 +26,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,
@@ -43,6 +43,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";
@@ -53,6 +54,7 @@ import type { CreateWorkspacePermissions } from "./permissions";
interface CreateWorkspacePageViewExperimentalProps {
autofillParameters: AutofillBuildParameter[];
canUpdateTemplate?: boolean;
creatingWorkspace: boolean;
defaultName?: string | null;
defaultOwner: TypesGen.User;
@@ -84,6 +86,7 @@ export const CreateWorkspacePageViewExperimental: FC<
CreateWorkspacePageViewExperimentalProps
> = ({
autofillParameters,
canUpdateTemplate,
creatingWorkspace,
defaultName,
defaultOwner,
@@ -378,6 +381,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>
@@ -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<