Skip to content

feat: add link to provisioner jobs and daemons #17509

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 14 additions & 13 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -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;
};
Expand Down
17 changes: 11 additions & 6 deletions site/src/api/queries/organizations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { API, type GetProvisionerJobsParams } from "api/api";
import {
API,
type GetProvisionerDaemonsParams,
type GetProvisionerJobsParams,
} from "api/api";
import type {
CreateOrganizationRequest,
GroupSyncSettings,
Expand Down Expand Up @@ -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),
};
};

Expand Down
3 changes: 2 additions & 1 deletion site/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
},
Expand Down
2 changes: 1 addition & 1 deletion site/src/pages/CreateTokenPage/CreateTokenForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const meta: Meta<typeof PermissionPillsList> = {
],
parameters: {
chromatic: {
diffThreshold: 0.5,
diffThreshold: 0.6,
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -114,19 +116,36 @@ 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>

<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const meta: Meta<typeof OrganizationProvisionerJobsPageView> = {
args: {
organization: MockOrganization,
jobs: MockProvisionerJobs,
filter: { status: "" },
filter: { status: "", ids: "" },
onRetry: fn(),
},
};
Expand Down Expand Up @@ -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,
Expand All @@ -109,3 +109,13 @@ export const OnFilter: Story = {
await userEvent.click(option);
},
};

export const FilterByID: Story = {
args: {
jobs: [MockProvisionerJob],
filter: {
ids: MockProvisionerJob.id,
status: "",
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -64,6 +72,7 @@ const StatusFilters: ProvisionerJobStatus[] = [

type JobProvisionersFilter = {
status: string;
ids: string;
};

type OrganizationProvisionerJobsPageViewProps = {
Expand Down Expand Up @@ -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>
Expand All @@ -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}>
Expand Down
Loading
Loading