fix(site): allow only template admins/owners to navigate to template version pages by clicking VersionRow (#27550)

fixes DEVEX-486

From `TemplateVersionsPage`
(/templates/:organization/:template/versions), users navigate to
individual template version pages (`TemplateVersionPage`, i.e.,
/templates/:organization/:template/versions/:version) by clicking
`VersionRow`.

This PR makes this click-to-navigate behavior on `VersionRow` available
to admin/owner users only, per @matifali's suggestion
This commit is contained in:
Andrew Aquino
2026-08-04 13:27:00 -07:00
committed by GitHub
parent d40c4f77b9
commit ca3de3c8c5
2 changed files with 34 additions and 1 deletions
@@ -7,6 +7,7 @@ import { InfoTooltip } from "#/components/InfoTooltip/InfoTooltip";
import { Pill } from "#/components/Pill/Pill";
import { TableCell } from "#/components/Table/Table";
import { TimelineEntry } from "#/components/Timeline/TimelineEntry";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { useClickableTableRow } from "#/hooks/useClickableTableRow";
interface VersionRowProps {
@@ -25,6 +26,7 @@ export const VersionRow: FC<VersionRowProps> = ({
onArchiveClick,
}) => {
const navigate = useNavigate();
const { permissions } = useAuthenticated();
const clickableProps = useClickableTableRow({
onClick: () => navigate(version.name),
@@ -33,7 +35,11 @@ export const VersionRow: FC<VersionRowProps> = ({
const jobStatus = version.job.status;
return (
<TimelineEntry data-testid={`version-${version.id}`} {...clickableProps}>
<TimelineEntry
data-testid={`version-${version.id}`}
aria-label={version.name}
{...(permissions.updateTemplates ? clickableProps : { clickable: false })}
>
<TableCell className="relative border-b-0 !p-0">
<div className="flex flex-row items-center justify-between gap-4 px-8 py-4">
<div className="flex flex-row items-center gap-4">
@@ -1,22 +1,32 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { action } from "storybook/actions";
import { within } from "storybook/test";
import {
MockCanceledProvisionerJob,
MockCancelingProvisionerJob,
MockFailedProvisionerJob,
MockNoPermissions,
MockPendingProvisionerJob,
MockPermissions,
MockRunningProvisionerJob,
MockTemplateVersion,
MockUserOwner,
} from "#/testHelpers/entities";
import { withAuthProvider } from "#/testHelpers/storybook";
import { VersionsTable } from "./VersionsTable";
const meta: Meta<typeof VersionsTable> = {
title: "pages/TemplatePage/VersionsTable",
component: VersionsTable,
parameters: {
user: MockUserOwner,
permissions: MockPermissions,
},
args: {
onPromoteClick: () => {},
onArchiveClick: () => {},
},
decorators: [withAuthProvider],
};
export default meta;
@@ -35,6 +45,23 @@ export const Example: Story = {
MockTemplateVersion,
],
},
play: async ({ canvasElement }) => {
// With permissions.updateTemplates, VersionRow is a clickable button
const canvas = within(canvasElement);
await canvas.findByRole("button", { name: MockTemplateVersion.name });
},
};
export const NoUpdatePermission: Story = {
args: { ...Example.args },
parameters: {
permissions: MockNoPermissions,
},
play: async ({ canvasElement }) => {
// Without permissions.updateTemplates, VersionRow is a non-clickable row
const canvas = within(canvasElement);
await canvas.findByRole("row", { name: MockTemplateVersion.name });
},
};
export const NoEditPermission: Story = {