mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2026-04-24 11:18:35 -05:00
Tasks marked as skipped in the UI (#2978)
* indicate skipped task runs correctly in the UI * do not use useCurrentTenantId * naming * new hook for task skipped
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { useIsTaskRunSkipped } from '../../hooks/use-is-task-run-skipped';
|
||||
import { useWorkflowDetails } from '../../hooks/use-workflow-details';
|
||||
import { TabOption } from './step-run-detail/step-run-detail';
|
||||
import StepRunNode from './step-run-node';
|
||||
import { useRefetchInterval } from '@/contexts/refetch-interval-context';
|
||||
import { queries, WorkflowRunShapeItemForWorkflowRunDetails } from '@/lib/api';
|
||||
import {
|
||||
queries,
|
||||
V1TaskEventType,
|
||||
WorkflowRunShapeItemForWorkflowRunDetails,
|
||||
} from '@/lib/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -17,7 +22,23 @@ type NodeRelationship = {
|
||||
};
|
||||
|
||||
export const JobMiniMap = ({ onClick }: JobMiniMapProps) => {
|
||||
const { shape, taskRuns: tasks, isLoading, isError } = useWorkflowDetails();
|
||||
const {
|
||||
shape,
|
||||
taskRuns: tasks,
|
||||
taskEvents,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useWorkflowDetails();
|
||||
|
||||
const skippedTaskIds = useMemo(() => {
|
||||
const ids = new Set<string>();
|
||||
for (const event of taskEvents) {
|
||||
if (event.eventType === V1TaskEventType.SKIPPED) {
|
||||
ids.add(event.taskId);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}, [taskEvents]);
|
||||
|
||||
const taskRunRelationships: NodeRelationship[] = useMemo(() => {
|
||||
if (!shape || !tasks) {
|
||||
@@ -127,6 +148,9 @@ export const JobMiniMap = ({ onClick }: JobMiniMapProps) => {
|
||||
onClick: () => onClick(taskRun?.metadata.id),
|
||||
childWorkflowsCount: taskRun?.numSpawnedChildren || 0,
|
||||
taskName: shapeItem.taskName,
|
||||
isSkipped: taskRun
|
||||
? skippedTaskIds.has(taskRun.metadata.id)
|
||||
: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -160,6 +184,7 @@ export const TaskRunMiniMap = ({
|
||||
taskRunId,
|
||||
}: JobMiniMapProps & UseTaskRunProps) => {
|
||||
const { taskRun, isLoading, isError } = useTaskRun({ taskRunId });
|
||||
const { isSkipped } = useIsTaskRunSkipped({ taskRunId, limit: 1 });
|
||||
|
||||
if (isLoading || isError || !taskRun) {
|
||||
return null;
|
||||
@@ -175,6 +200,7 @@ export const TaskRunMiniMap = ({
|
||||
onClick: () => onClick(taskRun.metadata.id),
|
||||
childWorkflowsCount: taskRun.numSpawnedChildren,
|
||||
taskName: taskRun.displayName,
|
||||
isSkipped,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+15
-12
@@ -1,6 +1,7 @@
|
||||
import { V1RunIndicator } from '../../../components/run-statuses';
|
||||
import { RunsTable } from '../../../components/runs-table';
|
||||
import { RunsProvider } from '../../../hooks/runs-provider';
|
||||
import { useIsTaskRunSkipped } from '../../../hooks/use-is-task-run-skipped';
|
||||
import { isTerminalState } from '../../../hooks/use-workflow-details';
|
||||
import { TaskRunMiniMap } from '../mini-map';
|
||||
import { StepRunEvents } from '../step-run-events-for-workflow-run';
|
||||
@@ -20,14 +21,13 @@ import {
|
||||
TabsTrigger,
|
||||
} from '@/components/v1/ui/tabs';
|
||||
import { useSidePanel } from '@/hooks/use-side-panel';
|
||||
import { useCurrentTenantId } from '@/hooks/use-tenant';
|
||||
import { V1TaskStatus, V1TaskSummary, queries } from '@/lib/api';
|
||||
import { emptyGolangUUID, formatDuration } from '@/lib/utils';
|
||||
import { TaskRunActionButton } from '@/pages/main/v1/task-runs-v1/actions';
|
||||
import { WorkflowDefinitionLink } from '@/pages/main/workflow-runs/$run/v2components/workflow-definition';
|
||||
import { appRoutes } from '@/router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { Link, useParams } from '@tanstack/react-router';
|
||||
import { FullscreenIcon } from 'lucide-react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
@@ -59,13 +59,13 @@ const TaskRunPermalinkOrBacklink = ({
|
||||
taskRun: V1TaskSummary;
|
||||
showViewTaskRunButton: boolean;
|
||||
}) => {
|
||||
const { tenantId } = useCurrentTenantId();
|
||||
const { tenant } = useParams({ from: appRoutes.tenantRoute.to });
|
||||
|
||||
if (showViewTaskRunButton) {
|
||||
return (
|
||||
<Link
|
||||
to={appRoutes.tenantRunRoute.to}
|
||||
params={{ tenant: tenantId, run: taskRun.metadata.id }}
|
||||
params={{ tenant: tenant, run: taskRun.metadata.id }}
|
||||
>
|
||||
<Button
|
||||
size={'sm'}
|
||||
@@ -84,7 +84,7 @@ const TaskRunPermalinkOrBacklink = ({
|
||||
return (
|
||||
<Link
|
||||
to={appRoutes.tenantRunRoute.to}
|
||||
params={{ tenant: tenantId, run: taskRun.workflowRunExternalId }}
|
||||
params={{ tenant: tenant, run: taskRun.workflowRunExternalId }}
|
||||
>
|
||||
<Button
|
||||
size={'sm'}
|
||||
@@ -133,6 +133,7 @@ export const TaskRunDetail = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { isSkipped } = useIsTaskRunSkipped({ taskRunId });
|
||||
const taskRun = taskRunQuery.data;
|
||||
|
||||
if (taskRunQuery.isLoading) {
|
||||
@@ -152,7 +153,9 @@ export const TaskRunDetail = ({
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex w-full flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
{taskRun.status && <V1RunIndicator status={taskRun.status} />}
|
||||
{taskRun.status && (
|
||||
<V1RunIndicator status={taskRun.status} isSkipped={isSkipped} />
|
||||
)}
|
||||
<h3 className="flex flex-row items-center gap-4 font-mono text-lg font-semibold leading-tight tracking-tight text-foreground">
|
||||
{taskRun.displayName || 'Task Run Detail'}
|
||||
</h3>
|
||||
@@ -162,7 +165,7 @@ export const TaskRunDetail = ({
|
||||
|
||||
{taskRun.parentTaskExternalId && (
|
||||
<TriggeringParentWorkflowRunSection
|
||||
tenantId={taskRun.tenantId}
|
||||
tenant={taskRun.tenantId}
|
||||
parentTaskExternalId={taskRun.parentTaskExternalId}
|
||||
/>
|
||||
)}
|
||||
@@ -329,9 +332,9 @@ const V1StepRunSummary = ({ taskRunId }: { taskRunId: string }) => {
|
||||
const taskRunQuery = useQuery({
|
||||
...queries.v1Tasks.get(taskRunId),
|
||||
});
|
||||
const { isSkipped: hasSkippedEvent } = useIsTaskRunSkipped({ taskRunId });
|
||||
|
||||
const timings = [];
|
||||
|
||||
const data = taskRunQuery.data;
|
||||
|
||||
if (taskRunQuery.isLoading || !data) {
|
||||
@@ -359,7 +362,7 @@ const V1StepRunSummary = ({ taskRunId }: { taskRunId: string }) => {
|
||||
if (data.status === V1TaskStatus.COMPLETED && data.finishedAt) {
|
||||
timings.push(
|
||||
<div key="finished" className="text-sm text-muted-foreground">
|
||||
{'Succeeded '}
|
||||
{hasSkippedEvent ? 'Skipped ' : 'Succeeded '}
|
||||
<RelativeDate date={data.finishedAt} />
|
||||
</div>,
|
||||
);
|
||||
@@ -393,10 +396,10 @@ const V1StepRunSummary = ({ taskRunId }: { taskRunId: string }) => {
|
||||
};
|
||||
|
||||
function TriggeringParentWorkflowRunSection({
|
||||
tenantId,
|
||||
tenant,
|
||||
parentTaskExternalId,
|
||||
}: {
|
||||
tenantId: string;
|
||||
tenant: string;
|
||||
parentTaskExternalId: string;
|
||||
}) {
|
||||
// Get the parent task to find the parent workflow run
|
||||
@@ -428,7 +431,7 @@ function TriggeringParentWorkflowRunSection({
|
||||
<Link
|
||||
to={appRoutes.tenantRunRoute.to}
|
||||
params={{
|
||||
tenant: tenantId,
|
||||
tenant: tenant,
|
||||
run: parentWorkflowRun.workflowRunExternalId,
|
||||
}}
|
||||
className="font-semibold text-indigo-500 hover:underline dark:text-indigo-200"
|
||||
|
||||
@@ -17,6 +17,7 @@ export type NodeData = {
|
||||
onClick: (defaultOpenTab?: TabOption) => void;
|
||||
childWorkflowsCount: number;
|
||||
taskName: string;
|
||||
isSkipped?: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
@@ -61,7 +62,10 @@ export default memo(({ data }: { data: NodeData }) => {
|
||||
<span className="step-run-backdrop absolute inset-[1px] bg-background transition-colors duration-200" />
|
||||
<div className="z-10 flex w-full flex-row items-center justify-between gap-4">
|
||||
<div className="z-10 flex flex-row items-center justify-start gap-2">
|
||||
<V1RunIndicator status={data.taskRun?.status} />
|
||||
<V1RunIndicator
|
||||
status={data.taskRun?.status}
|
||||
isSkipped={data.isSkipped}
|
||||
/>
|
||||
<div className="max-w-[160px] flex-grow truncate">
|
||||
{data.taskName}
|
||||
</div>
|
||||
|
||||
+15
-3
@@ -1,7 +1,7 @@
|
||||
import { useWorkflowDetails } from '../../hooks/use-workflow-details';
|
||||
import stepRunNode, { NodeData } from './step-run-node';
|
||||
import { useTheme } from '@/components/hooks/use-theme';
|
||||
import { V1TaskStatus } from '@/lib/api';
|
||||
import { V1TaskEventType, V1TaskStatus } from '@/lib/api';
|
||||
import dagre from 'dagre';
|
||||
import { useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
@@ -30,7 +30,18 @@ const WorkflowRunVisualizer = ({
|
||||
setSelectedTaskRunId: (id: string) => void;
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const { shape, taskRuns, isLoading, isError } = useWorkflowDetails();
|
||||
const { shape, taskRuns, taskEvents, isLoading, isError } =
|
||||
useWorkflowDetails();
|
||||
|
||||
const skippedTaskIds = useMemo(() => {
|
||||
const ids = new Set<string>();
|
||||
for (const event of taskEvents) {
|
||||
if (event.eventType === V1TaskEventType.SKIPPED) {
|
||||
ids.add(event.taskId);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}, [taskEvents]);
|
||||
|
||||
const edges: Edge[] = useMemo(
|
||||
() =>
|
||||
@@ -85,6 +96,7 @@ const WorkflowRunVisualizer = ({
|
||||
onClick: () => task && setSelectedTaskRunId(task.metadata.id),
|
||||
childWorkflowsCount: task?.numSpawnedChildren || 0,
|
||||
taskName: shapeItem.taskName,
|
||||
isSkipped: task ? skippedTaskIds.has(task.metadata.id) : false,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -95,7 +107,7 @@ const WorkflowRunVisualizer = ({
|
||||
selectable: true,
|
||||
};
|
||||
}) || [],
|
||||
[shape, taskRuns, setSelectedTaskRunId],
|
||||
[shape, taskRuns, setSelectedTaskRunId, skippedTaskIds],
|
||||
);
|
||||
|
||||
const nodeWidth = 230;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { V1TaskStatus } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CircleMinus } from 'lucide-react';
|
||||
|
||||
function createV2IndicatorVariant(eventType: V1TaskStatus | undefined) {
|
||||
switch (eventType) {
|
||||
@@ -20,9 +21,15 @@ function createV2IndicatorVariant(eventType: V1TaskStatus | undefined) {
|
||||
|
||||
export function V1RunIndicator({
|
||||
status,
|
||||
isSkipped,
|
||||
}: {
|
||||
status: V1TaskStatus | undefined;
|
||||
isSkipped?: boolean;
|
||||
}) {
|
||||
if (isSkipped) {
|
||||
return <CircleMinus className="h-3 w-3 text-muted-foreground" />;
|
||||
}
|
||||
|
||||
const indicator = createV2IndicatorVariant(status);
|
||||
|
||||
return <div className={cn(indicator, 'h-[6px] w-[6px] rounded-full')} />;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { queries, V1TaskEventType } from '@/lib/api';
|
||||
import { appRoutes } from '@/router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useParams } from '@tanstack/react-router';
|
||||
|
||||
type UseIsTaskRunSkippedProps = {
|
||||
taskRunId: string;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export const useIsTaskRunSkipped = ({
|
||||
taskRunId,
|
||||
limit = 50,
|
||||
}: UseIsTaskRunSkippedProps) => {
|
||||
const { tenant } = useParams({ from: appRoutes.tenantRoute.to });
|
||||
const eventsQuery = useQuery({
|
||||
...queries.v1TaskEvents.list(tenant, { limit, offset: 0 }, taskRunId),
|
||||
});
|
||||
|
||||
const isSkipped =
|
||||
eventsQuery.data?.rows?.some(
|
||||
(event) => event.eventType === V1TaskEventType.SKIPPED,
|
||||
) ?? false;
|
||||
|
||||
return {
|
||||
isSkipped,
|
||||
isLoading: eventsQuery.isLoading,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user