chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
@@ -0,0 +1,352 @@
import {
BoltIcon,
BoltSlashIcon,
BookOpenIcon,
PencilSquareIcon,
TrashIcon,
} from "@heroicons/react/20/solid";
import { DialogDescription } from "@radix-ui/react-dialog";
import { type FetcherWithComponents, Form, useLocation } from "@remix-run/react";
import { type ReactNode } from "react";
import { InlineCode } from "~/components/code/InlineCode";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTrigger,
} from "~/components/primitives/Dialog";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { InfoPanel } from "~/components/primitives/InfoPanel";
import { Paragraph } from "~/components/primitives/Paragraph";
import * as Property from "~/components/primitives/PropertyTable";
import {
Table,
TableBlankRow,
TableBody,
TableCell,
TableHeader,
TableHeaderCell,
TableRow,
} from "~/components/primitives/Table";
import { EnabledStatus } from "~/components/runs/v3/EnabledStatus";
import { ScheduleTypeCombo } from "~/components/runs/v3/ScheduleType";
import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { cn } from "~/utils/cn";
import { v3EditSchedulePath } from "~/utils/pathBuilder";
type RunRow = React.ComponentProps<typeof TaskRunsTable>["runs"][number];
type EnvironmentRow = React.ComponentProps<typeof EnvironmentCombo>["environment"] & {
id: string;
};
export type ScheduleInspectorData = {
id: string;
friendlyId: string;
type: "DECLARATIVE" | "IMPERATIVE";
taskIdentifier: string;
cron: string;
cronDescription: string;
timezone: string;
externalId: string | null;
deduplicationKey: string | null;
userProvidedDeduplicationKey: boolean;
active: boolean;
environments: EnvironmentRow[];
runs: RunRow[];
nextRuns: Date[];
};
type Props = {
schedule: ScheduleInspectorData;
/**
* Right-aligned slot in the header (e.g. a back link or close button).
*/
headerActions?: ReactNode;
/**
* URL the action `Form`s post to. Defaults to the current page (Form's
* default behavior) — pass the schedule detail route when the inspector
* is rendered somewhere else (e.g. in a sheet on a different page).
*/
actionPath?: string;
/** When set, Edit calls back instead of navigating to the standalone edit page. */
onEdit?: () => void;
/** Submits enable/disable via this fetcher with `_format=json` so the host stays put. */
activeToggleFetcher?: FetcherWithComponents<unknown>;
/** Submits delete via this fetcher with `_format=json` so the host stays put. */
deleteFetcher?: FetcherWithComponents<unknown>;
};
export function ScheduleInspector({
schedule,
headerActions,
actionPath,
onEdit,
activeToggleFetcher,
deleteFetcher,
}: Props) {
const location = useLocation();
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
const isUtc = schedule.timezone === "UTC";
const isImperative = schedule.type === "IMPERATIVE";
return (
<div
className={cn(
"grid h-full max-h-full overflow-hidden bg-background-bright",
isImperative ? "grid-rows-[2.5rem_1fr_auto]" : "grid-rows-[2.5rem_1fr]"
)}
>
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<Header2 className="whitespace-nowrap">{schedule.friendlyId}</Header2>
{headerActions}
</div>
<div className="overflow-y-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
<div className="space-y-3">
<div className="p-3">
<Property.Table>
<Property.Item>
<Property.Label>Schedule ID</Property.Label>
<Property.Value>{schedule.friendlyId}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Task ID</Property.Label>
<Property.Value>{schedule.taskIdentifier}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Type</Property.Label>
<Property.Value>
<ScheduleTypeCombo type={schedule.type} className="text-sm" />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>CRON</Property.Label>
<Property.Value>
<div className="space-y-2">
<InlineCode variant="extra-small">{schedule.cron}</InlineCode>
<Paragraph variant="small">{schedule.cronDescription}</Paragraph>
</div>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Timezone</Property.Label>
<Property.Value>{schedule.timezone}</Property.Value>
</Property.Item>
<Property.Item className="gap-1">
<Property.Label>Environment</Property.Label>
<Property.Value>
<div className="flex flex-col gap-2">
{schedule.environments.map((env) => (
<EnvironmentCombo key={env.id} environment={env} className="text-xs" />
))}
</div>
</Property.Value>
</Property.Item>
{isImperative && (
<>
<Property.Item>
<Property.Label>External ID</Property.Label>
<Property.Value>
{schedule.externalId ? schedule.externalId : ""}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Deduplication key</Property.Label>
<Property.Value>
{schedule.userProvidedDeduplicationKey ? schedule.deduplicationKey : ""}
</Property.Value>
</Property.Item>
<Property.Item className="gap-1.5">
<Property.Label>Status</Property.Label>
<Property.Value>
<EnabledStatus enabled={schedule.active} />
</Property.Value>
</Property.Item>
</>
)}
</Property.Table>
</div>
<div className="flex flex-col gap-1">
<Header3 className="pb-1 pl-3">Last 5 runs</Header3>
<TaskRunsTable
total={schedule.runs.length}
hasFilters={false}
filters={{
tasks: [],
versions: [],
statuses: [],
from: undefined,
to: undefined,
}}
runs={schedule.runs}
isLoading={false}
variant="bright"
disableAdjacentRows
/>
</div>
<div className="flex flex-col gap-1 pt-2">
<Header3 className="pb-1 pl-3">Next 5 runs</Header3>
<Table variant="bright">
<TableHeader>
<TableRow>
{!isUtc && <TableHeaderCell>{schedule.timezone}</TableHeaderCell>}
<TableHeaderCell>UTC</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{schedule.active ? (
schedule.nextRuns.length ? (
schedule.nextRuns.map((run, index) => (
<TableRow key={index}>
{!isUtc && (
<TableCell>
<DateTime date={run} timeZone={schedule.timezone} />
</TableCell>
)}
<TableCell>
<DateTime date={run} timeZone="UTC" />
</TableCell>
</TableRow>
))
) : (
<TableBlankRow colSpan={isUtc ? 1 : 2}>
<PlaceholderText title="You found a bug" />
</TableBlankRow>
)
) : (
<TableBlankRow colSpan={isUtc ? 1 : 2}>
<PlaceholderText title="Schedule disabled" />
</TableBlankRow>
)}
</TableBody>
</Table>
</div>
{!isImperative && (
<div className="p-3">
<InfoPanel
title="Editing declarative schedules"
icon={BookOpenIcon}
iconClassName="text-indigo-500"
variant="info"
accessory={
<LinkButton
to="https://trigger.dev/docs/v3/tasks-scheduled"
variant="docs/small"
LeadingIcon={BookOpenIcon}
>
Schedules docs
</LinkButton>
}
panelClassName="max-w-full"
>
You can only edit a declarative schedule by updating your schedules.task and then
running the CLI dev and deploy commands.
</InfoPanel>
</div>
)}
</div>
</div>
{isImperative && (
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2 py-2">
<div className="flex items-center gap-2">
{(() => {
const ToggleForm = activeToggleFetcher?.Form ?? Form;
const isSubmitting = activeToggleFetcher?.state === "submitting";
return (
<ToggleForm method="post" action={actionPath}>
{activeToggleFetcher ? <input type="hidden" name="_format" value="json" /> : null}
<Button
type="submit"
variant="secondary/small"
LeadingIcon={schedule.active ? BoltSlashIcon : BoltIcon}
leadingIconClassName={schedule.active ? "text-dimmed" : "text-success"}
name="action"
value={schedule.active ? "disable" : "enable"}
disabled={isSubmitting}
>
{schedule.active ? "Disable" : "Enable"}
</Button>
</ToggleForm>
);
})()}
<Dialog>
<DialogTrigger asChild>
<Button
type="submit"
variant="danger/small"
LeadingIcon={TrashIcon}
name="action"
value="delete"
>
Delete
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-sm">
<DialogHeader>Delete schedule</DialogHeader>
<DialogDescription className="mt-3">
Are you sure you want to delete this schedule? This can't be reversed.
</DialogDescription>
<DialogFooter className="sm:justify-end">
{(() => {
const DeleteForm = deleteFetcher?.Form ?? Form;
const isSubmitting = deleteFetcher?.state === "submitting";
return (
<DeleteForm method="post" action={actionPath}>
{deleteFetcher ? <input type="hidden" name="_format" value="json" /> : null}
<Button
type="submit"
variant="danger/medium"
LeadingIcon={TrashIcon}
name="action"
value="delete"
disabled={isSubmitting}
>
Delete
</Button>
</DeleteForm>
);
})()}
</DialogFooter>
</DialogContent>
</Dialog>
</div>
<div className="flex items-center gap-4">
{onEdit ? (
<Button variant="secondary/small" LeadingIcon={PencilSquareIcon} onClick={onEdit}>
Edit schedule
</Button>
) : (
<LinkButton
variant="secondary/small"
to={`${v3EditSchedulePath(organization, project, environment, schedule)}${
location.search
}`}
LeadingIcon={PencilSquareIcon}
>
Edit schedule
</LinkButton>
)}
</div>
</div>
)}
</div>
);
}
function PlaceholderText({ title }: { title: string }) {
return (
<div className="flex items-center justify-center">
<Paragraph className="w-auto">{title}</Paragraph>
</div>
);
}