mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add link to provisioner jobs and daemons (#17509)
Close https://github.com/coder/coder/issues/17314 **Demo** https://github.com/user-attachments/assets/db37aa67-4755-4b72-a54d-2c3f0c297b7d **Changes** - Added the `xs` button variant - Display all the daemons - idle and offline - and set a size limit to 100 results (explanation in the demo) - Filter daemons and jobs by ID
This commit is contained in:
+14
-13
@@ -396,7 +396,17 @@ export class MissingBuildParameters extends Error {
|
||||
}
|
||||
|
||||
export type GetProvisionerJobsParams = {
|
||||
status?: TypesGen.ProvisionerJobStatus;
|
||||
status?: string;
|
||||
limit?: number;
|
||||
// IDs separated by comma
|
||||
ids?: string;
|
||||
};
|
||||
|
||||
export type GetProvisionerDaemonsParams = {
|
||||
// IDs separated by comma
|
||||
ids?: string;
|
||||
// Stringified JSON Object
|
||||
tags?: string;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
@@ -711,22 +721,13 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param organization Can be the organization's ID or name
|
||||
* @param tags to filter provisioner daemons by.
|
||||
*/
|
||||
getProvisionerDaemonsByOrganization = async (
|
||||
organization: string,
|
||||
tags?: Record<string, string>,
|
||||
params?: GetProvisionerDaemonsParams,
|
||||
): Promise<TypesGen.ProvisionerDaemon[]> => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (tags) {
|
||||
params.append("tags", JSON.stringify(tags));
|
||||
}
|
||||
|
||||
const response = await this.axios.get<TypesGen.ProvisionerDaemon[]>(
|
||||
`/api/v2/organizations/${organization}/provisionerdaemons?${params}`,
|
||||
`/api/v2/organizations/${organization}/provisionerdaemons`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { API, type GetProvisionerJobsParams } from "api/api";
|
||||
import {
|
||||
API,
|
||||
type GetProvisionerDaemonsParams,
|
||||
type GetProvisionerJobsParams,
|
||||
} from "api/api";
|
||||
import type {
|
||||
CreateOrganizationRequest,
|
||||
GroupSyncSettings,
|
||||
@@ -164,16 +168,17 @@ export const organizations = () => {
|
||||
|
||||
export const getProvisionerDaemonsKey = (
|
||||
organization: string,
|
||||
tags?: Record<string, string>,
|
||||
) => ["organization", organization, tags, "provisionerDaemons"];
|
||||
params?: GetProvisionerDaemonsParams,
|
||||
) => ["organization", organization, "provisionerDaemons", params];
|
||||
|
||||
export const provisionerDaemons = (
|
||||
organization: string,
|
||||
tags?: Record<string, string>,
|
||||
params?: GetProvisionerDaemonsParams,
|
||||
) => {
|
||||
return {
|
||||
queryKey: getProvisionerDaemonsKey(organization, tags),
|
||||
queryFn: () => API.getProvisionerDaemonsByOrganization(organization, tags),
|
||||
queryKey: getProvisionerDaemonsKey(organization, params),
|
||||
queryFn: () =>
|
||||
API.getProvisionerDaemonsByOrganization(organization, params),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { forwardRef } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
`inline-flex items-center justify-center gap-1 whitespace-nowrap
|
||||
`inline-flex items-center justify-center gap-1 whitespace-nowrap font-sans
|
||||
border-solid rounded-md transition-colors
|
||||
text-sm font-semibold font-medium cursor-pointer no-underline
|
||||
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
|
||||
@@ -30,6 +30,7 @@ export const buttonVariants = cva(
|
||||
size: {
|
||||
lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg",
|
||||
sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm",
|
||||
xs: "min-w-8 py-1 px-2 text-2xs rounded-md",
|
||||
icon: "size-8 px-1.5 [&_svg]:size-icon-sm",
|
||||
"icon-lg": "size-10 px-2 [&_svg]:size-icon-lg",
|
||||
},
|
||||
|
||||
@@ -119,7 +119,6 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
|
||||
|
||||
{lifetimeDays === "custom" && (
|
||||
<TextField
|
||||
data-chromatic="ignore"
|
||||
type="date"
|
||||
label="Expires on"
|
||||
defaultValue={dayjs().add(expDays, "day").format("YYYY-MM-DD")}
|
||||
@@ -130,6 +129,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
|
||||
setExpDays(lt);
|
||||
}}
|
||||
inputProps={{
|
||||
"data-chromatic": "ignore",
|
||||
min: dayjs().add(1, "day").format("YYYY-MM-DD"),
|
||||
max: maxTokenLifetime
|
||||
? dayjs()
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ const meta: Meta<typeof PermissionPillsList> = {
|
||||
],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
diffThreshold: 0.5,
|
||||
diffThreshold: 0.6,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,17 +15,19 @@ import {
|
||||
ProvisionerTruncateTags,
|
||||
} from "modules/provisioners/ProvisionerTags";
|
||||
import { type FC, useState } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { cn } from "utils/cn";
|
||||
import { relativeTime } from "utils/time";
|
||||
import { CancelJobButton } from "./CancelJobButton";
|
||||
|
||||
type JobRowProps = {
|
||||
job: ProvisionerJob;
|
||||
defaultIsOpen: boolean;
|
||||
};
|
||||
|
||||
export const JobRow: FC<JobRowProps> = ({ job }) => {
|
||||
export const JobRow: FC<JobRowProps> = ({ job, defaultIsOpen = false }) => {
|
||||
const metadata = job.metadata;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(defaultIsOpen);
|
||||
const queue = {
|
||||
size: job.queue_size,
|
||||
position: job.queue_position,
|
||||
@@ -114,8 +116,21 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
|
||||
: "[]"}
|
||||
</dd>
|
||||
|
||||
<dt>Completed by provisioner:</dt>
|
||||
<dd>{job.worker_id}</dd>
|
||||
{job.worker_id && (
|
||||
<>
|
||||
<dt>Completed by provisioner:</dt>
|
||||
<dd className="flex items-center gap-2">
|
||||
<span>{job.worker_id}</span>
|
||||
<Button size="xs" variant="outline" asChild>
|
||||
<RouterLink
|
||||
to={`../provisioners?${new URLSearchParams({ ids: job.worker_id })}`}
|
||||
>
|
||||
View provisioner
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
|
||||
<dt>Associated workspace:</dt>
|
||||
<dd>{job.metadata.workspace_name ?? "null"}</dd>
|
||||
@@ -123,10 +138,14 @@ export const JobRow: FC<JobRowProps> = ({ job }) => {
|
||||
<dt>Creation time:</dt>
|
||||
<dd data-chromatic="ignore">{job.created_at}</dd>
|
||||
|
||||
<dt>Queue:</dt>
|
||||
<dd>
|
||||
{job.queue_position}/{job.queue_size}
|
||||
</dd>
|
||||
{job.queue_position > 0 && (
|
||||
<>
|
||||
<dt>Queue:</dt>
|
||||
<dd>
|
||||
{job.queue_position}/{job.queue_size}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
|
||||
<dt>Tags:</dt>
|
||||
<dd>
|
||||
|
||||
+6
-6
@@ -11,18 +11,18 @@ const OrganizationProvisionerJobsPage: FC = () => {
|
||||
const { organization } = useOrganizationSettings();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const filter = {
|
||||
status: searchParams.get("status") || "",
|
||||
status: searchParams.get("status") ?? "",
|
||||
ids: searchParams.get("ids") ?? "",
|
||||
};
|
||||
const queryParams = {
|
||||
...filter,
|
||||
limit: 100,
|
||||
} as GetProvisionerJobsParams;
|
||||
const {
|
||||
data: jobs,
|
||||
isLoadingError,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
...provisionerJobs(organization?.id || "", queryParams),
|
||||
...provisionerJobs(organization?.id ?? "", {
|
||||
...filter,
|
||||
limit: 100,
|
||||
}),
|
||||
enabled: organization !== undefined,
|
||||
});
|
||||
|
||||
|
||||
+13
-3
@@ -21,7 +21,7 @@ const meta: Meta<typeof OrganizationProvisionerJobsPageView> = {
|
||||
args: {
|
||||
organization: MockOrganization,
|
||||
jobs: MockProvisionerJobs,
|
||||
filter: { status: "" },
|
||||
filter: { status: "", ids: "" },
|
||||
onRetry: fn(),
|
||||
},
|
||||
};
|
||||
@@ -81,8 +81,8 @@ export const Empty: Story = {
|
||||
export const OnFilter: Story = {
|
||||
render: function FilterWithState({ ...args }) {
|
||||
const [jobs, setJobs] = useState<ProvisionerJob[]>([]);
|
||||
const [filter, setFilter] = useState({ status: "pending" });
|
||||
const handleFilterChange = (newFilter: { status: string }) => {
|
||||
const [filter, setFilter] = useState({ status: "pending", ids: "" });
|
||||
const handleFilterChange = (newFilter: { status: string; ids: string }) => {
|
||||
setFilter(newFilter);
|
||||
const filteredJobs = MockProvisionerJobs.filter((job) =>
|
||||
newFilter.status ? job.status === newFilter.status : true,
|
||||
@@ -109,3 +109,13 @@ export const OnFilter: Story = {
|
||||
await userEvent.click(option);
|
||||
},
|
||||
};
|
||||
|
||||
export const FilterByID: Story = {
|
||||
args: {
|
||||
jobs: [MockProvisionerJob],
|
||||
filter: {
|
||||
ids: MockProvisionerJob.id,
|
||||
status: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+72
-25
@@ -3,6 +3,7 @@ import type {
|
||||
ProvisionerJob,
|
||||
ProvisionerJobStatus,
|
||||
} from "api/typesGenerated";
|
||||
import { Badge } from "components/Badge/Badge";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Link } from "components/Link/Link";
|
||||
@@ -33,6 +34,13 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "components/Table/Table";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { XIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { docs } from "utils/docs";
|
||||
@@ -64,6 +72,7 @@ const StatusFilters: ProvisionerJobStatus[] = [
|
||||
|
||||
type JobProvisionersFilter = {
|
||||
status: string;
|
||||
ids: string;
|
||||
};
|
||||
|
||||
type OrganizationProvisionerJobsPageViewProps = {
|
||||
@@ -110,30 +119,62 @@ const OrganizationProvisionerJobsPageView: FC<
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
<Select
|
||||
value={filter.status}
|
||||
onValueChange={(status) => {
|
||||
onFilterChange({ status: status as ProvisionerJobStatus });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]" data-testid="status-filter">
|
||||
<SelectValue placeholder="All statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{StatusFilters.map((status) => (
|
||||
<SelectItem key={status} value={status}>
|
||||
<StatusIndicator variant={variantByStatus[status]}>
|
||||
<StatusIndicatorDot />
|
||||
<span className="block first-letter:uppercase">
|
||||
{status}
|
||||
</span>
|
||||
</StatusIndicator>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center gap-2">
|
||||
{filter.ids && (
|
||||
<div className="relative">
|
||||
<Badge className="h-10 text-sm pl-3 pr-10 font-mono">
|
||||
{filter.ids}
|
||||
</Badge>
|
||||
<div className="size-10 flex items-center justify-center absolute top-0 right-0">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
onFilterChange({ ...filter, ids: "" });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Clear ID</span>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clear ID</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Select
|
||||
value={filter.status}
|
||||
onValueChange={(status) => {
|
||||
onFilterChange({
|
||||
...filter,
|
||||
status,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]" data-testid="status-filter">
|
||||
<SelectValue placeholder="All statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{StatusFilters.map((status) => (
|
||||
<SelectItem key={status} value={status}>
|
||||
<StatusIndicator variant={variantByStatus[status]}>
|
||||
<StatusIndicatorDot />
|
||||
<span className="block first-letter:uppercase">
|
||||
{status}
|
||||
</span>
|
||||
</StatusIndicator>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Table className="mt-6">
|
||||
<TableHeader>
|
||||
@@ -149,7 +190,13 @@ const OrganizationProvisionerJobsPageView: FC<
|
||||
<TableBody>
|
||||
{jobs ? (
|
||||
jobs.length > 0 ? (
|
||||
jobs.map((j) => <JobRow key={j.id} job={j} />)
|
||||
jobs.map((j) => (
|
||||
<JobRow
|
||||
defaultIsOpen={filter.ids.includes(j.id)}
|
||||
key={j.id}
|
||||
job={j}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
|
||||
+12
-4
@@ -8,7 +8,7 @@ import { RequirePermission } from "modules/permissions/RequirePermission";
|
||||
import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
|
||||
|
||||
@@ -16,14 +16,20 @@ const OrganizationProvisionersPage: FC = () => {
|
||||
const { organization: organizationName } = useParams() as {
|
||||
organization: string;
|
||||
};
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const queryParams = {
|
||||
ids: searchParams.get("ids") ?? "",
|
||||
tags: searchParams.get("tags") ?? "",
|
||||
};
|
||||
const { organization, organizationPermissions } = useOrganizationSettings();
|
||||
const { entitlements } = useDashboard();
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
const provisionersQuery = useQuery({
|
||||
...provisionerDaemons(organizationName),
|
||||
select: (provisioners) =>
|
||||
provisioners.filter((p) => p.status !== "offline"),
|
||||
...provisionerDaemons(organizationName, {
|
||||
...queryParams,
|
||||
limit: 100,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
@@ -59,6 +65,8 @@ const OrganizationProvisionersPage: FC = () => {
|
||||
provisioners={provisionersQuery.data}
|
||||
buildVersion={buildInfoQuery.data?.version}
|
||||
onRetry={provisionersQuery.refetch}
|
||||
filter={queryParams}
|
||||
onFilterChange={setSearchParams}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+12
@@ -24,6 +24,9 @@ const meta: Meta<typeof OrganizationProvisionersPageView> = {
|
||||
version: "0.0.0",
|
||||
},
|
||||
],
|
||||
filter: {
|
||||
ids: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,3 +63,12 @@ export const Paywall: Story = {
|
||||
showPaywall: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const FilterByID: Story = {
|
||||
args: {
|
||||
provisioners: [MockProvisioner],
|
||||
filter: {
|
||||
ids: MockProvisioner.id,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+53
-2
@@ -1,4 +1,5 @@
|
||||
import type { ProvisionerDaemon } from "api/typesGenerated";
|
||||
import { Badge } from "components/Badge/Badge";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Link } from "components/Link/Link";
|
||||
@@ -17,23 +18,43 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "components/Table/Table";
|
||||
import { SquareArrowOutUpRightIcon } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { SquareArrowOutUpRightIcon, XIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
import { LastConnectionHead } from "./LastConnectionHead";
|
||||
import { ProvisionerRow } from "./ProvisionerRow";
|
||||
|
||||
type ProvisionersFilter = {
|
||||
ids: string;
|
||||
};
|
||||
|
||||
interface OrganizationProvisionersPageViewProps {
|
||||
showPaywall: boolean | undefined;
|
||||
provisioners: readonly ProvisionerDaemon[] | undefined;
|
||||
buildVersion: string | undefined;
|
||||
error: unknown;
|
||||
filter: ProvisionersFilter;
|
||||
onRetry: () => void;
|
||||
onFilterChange: (filter: ProvisionersFilter) => void;
|
||||
}
|
||||
|
||||
export const OrganizationProvisionersPageView: FC<
|
||||
OrganizationProvisionersPageViewProps
|
||||
> = ({ showPaywall, error, provisioners, buildVersion, onRetry }) => {
|
||||
> = ({
|
||||
showPaywall,
|
||||
error,
|
||||
provisioners,
|
||||
buildVersion,
|
||||
filter,
|
||||
onFilterChange,
|
||||
onRetry,
|
||||
}) => {
|
||||
return (
|
||||
<section>
|
||||
<SettingsHeader>
|
||||
@@ -45,6 +66,35 @@ export const OrganizationProvisionersPageView: FC<
|
||||
</SettingsHeaderDescription>
|
||||
</SettingsHeader>
|
||||
|
||||
{filter.ids && (
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<div className="relative">
|
||||
<Badge className="h-10 text-sm pl-3 pr-10 font-mono">
|
||||
{filter.ids}
|
||||
</Badge>
|
||||
<div className="size-10 flex items-center justify-center absolute top-0 right-0">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
onFilterChange({ ...filter, ids: "" });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Clear ID</span>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clear ID</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showPaywall ? (
|
||||
<Paywall
|
||||
message="Provisioners"
|
||||
@@ -73,6 +123,7 @@ export const OrganizationProvisionersPageView: FC<
|
||||
provisioner={provisioner}
|
||||
key={provisioner.id}
|
||||
buildVersion={buildVersion}
|
||||
defaultIsOpen={filter.ids.includes(provisioner.id)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
||||
+14
-2
@@ -18,6 +18,7 @@ import {
|
||||
} from "modules/provisioners/ProvisionerTags";
|
||||
import { ProvisionerKey } from "pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerKey";
|
||||
import { type FC, useState } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { cn } from "utils/cn";
|
||||
import { relativeTime } from "utils/time";
|
||||
import { ProvisionerVersion } from "./ProvisionerVersion";
|
||||
@@ -34,13 +35,15 @@ const variantByStatus: Record<
|
||||
type ProvisionerRowProps = {
|
||||
provisioner: ProvisionerDaemon;
|
||||
buildVersion: string | undefined;
|
||||
defaultIsOpen: boolean;
|
||||
};
|
||||
|
||||
export const ProvisionerRow: FC<ProvisionerRowProps> = ({
|
||||
provisioner,
|
||||
buildVersion,
|
||||
defaultIsOpen = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(defaultIsOpen);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -151,7 +154,16 @@ export const ProvisionerRow: FC<ProvisionerRowProps> = ({
|
||||
{provisioner.previous_job && (
|
||||
<>
|
||||
<dt>Previous job:</dt>
|
||||
<dd>{provisioner.previous_job.id}</dd>
|
||||
<dd className="flex items-center gap-2">
|
||||
<span>{provisioner.previous_job.id}</span>
|
||||
<Button size="xs" variant="outline" asChild>
|
||||
<RouterLink
|
||||
to={`../provisioner-jobs?${new URLSearchParams({ ids: provisioner.previous_job.id })}`}
|
||||
>
|
||||
View job
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</dd>
|
||||
|
||||
<dt>Previous job status:</dt>
|
||||
<dd>
|
||||
|
||||
@@ -91,7 +91,7 @@ const meta = {
|
||||
},
|
||||
],
|
||||
chromatic: {
|
||||
diffThreshold: 0.5,
|
||||
diffThreshold: 0.8,
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
|
||||
@@ -39,7 +39,7 @@ const meta: Meta<typeof WorkspaceTopbar> = {
|
||||
layout: "fullscreen",
|
||||
features: ["advanced_template_scheduling"],
|
||||
chromatic: {
|
||||
diffThreshold: 0.3,
|
||||
diffThreshold: 0.6,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -321,7 +321,7 @@ export const TemplateInfoPopover: Story = {
|
||||
},
|
||||
parameters: {
|
||||
chromatic: {
|
||||
diffThreshold: 0.3,
|
||||
diffThreshold: 0.6,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,6 +8,9 @@ module.exports = {
|
||||
important: ["#root", "#storybook-root"],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: `"Inter Variable", system-ui, sans-serif`,
|
||||
},
|
||||
size: {
|
||||
"icon-lg": "1.5rem",
|
||||
"icon-sm": "1.125rem",
|
||||
|
||||
Reference in New Issue
Block a user