diff --git a/src/ui/common/src/components/pages/workflow/id/hook.ts b/src/ui/common/src/components/pages/workflow/id/hook.ts index 3ed572287..145941fde 100644 --- a/src/ui/common/src/components/pages/workflow/id/hook.ts +++ b/src/ui/common/src/components/pages/workflow/id/hook.ts @@ -12,6 +12,7 @@ import { import { NodeResultsMap, NodesMap } from '../../../../handlers/responses/node'; import { DagResultResponse } from '../../../../handlers/responses/workflow'; import { getPathPrefix } from '../../../../utils/getPathPrefix'; +import { getLatestDagResult } from '../../../../utils/shared'; export type useWorkflowIdsOutputs = { workflowId: string; @@ -187,3 +188,34 @@ export function useWorkflowNodesResults( ), }; } + +export function useLatestDagResultOrDag(apiKey: string, workflowId: string) { + const { + data: dagResults, + error: dagResultsError, + isLoading: dagResultsLoading, + } = useDagResultsGetQuery({ + apiKey: apiKey, + workflowId: workflowId, + }); + + const latestDagResult = getLatestDagResult(dagResults ?? []); // undefined if not available + + const dagIdFromLatestDagResult = latestDagResult?.dag_id; + + const { + data: dags, + error: dagsError, + isLoading: dagsLoading, + } = useDagsGetQuery( + { + apiKey: apiKey, + workflowId: workflowId, + }, + { + skip: dagIdFromLatestDagResult, + } + ); + + return { latestDagResult, dag: (dags ?? [])[0] }; +} diff --git a/src/ui/common/src/components/pages/workflows/index.tsx b/src/ui/common/src/components/pages/workflows/index.tsx index a456afad2..e9fecceaf 100644 --- a/src/ui/common/src/components/pages/workflows/index.tsx +++ b/src/ui/common/src/components/pages/workflows/index.tsx @@ -9,7 +9,7 @@ import { } from '../../../handlers/AqueductApi'; import UserProfile from '../../../utils/auth'; import getPathPrefix from '../../../utils/getPathPrefix'; -import ExecutionStatus from '../../../utils/shared'; +import ExecutionStatus, { getLatestDagResult } from '../../../utils/shared'; import { getWorkflowEngineTypes } from '../../../utils/workflows'; import DefaultLayout from '../../layouts/default'; import { BreadcrumbLink } from '../../layouts/NavBar'; @@ -18,7 +18,11 @@ import { SortType, } from '../../tables/PaginatedSearchTable'; import { LayoutProps } from '../types'; -import { useWorkflowNodes, useWorkflowNodesResults } from '../workflow/id/hook'; +import { + useLatestDagResultOrDag, + useWorkflowNodes, + useWorkflowNodesResults, +} from '../workflow/id/hook'; import CheckItem from './components/CheckItem'; import ExecutionStatusLink from './components/ExecutionStatusLink'; import MetricItem from './components/MetricItem'; @@ -84,22 +88,6 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { }, ]; - const getLatestDagResult = (dagResults) => - dagResults.reduce( - (prev, curr) => - curr.exec_state?.timestamps?.pending_at - ? new Date(prev.exec_state?.timestamps?.pending_at) < - new Date(curr.exec_state?.timestamps?.pending_at) - ? curr - : prev - : curr, - { - exec_state: { - status: ExecutionStatus.Registered, - timestamps: { pending_at: 0 }, - }, - } - ); const LastRunComponent = (row) => { const workflowId = row.id; @@ -133,43 +121,30 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { const workflowId = row.id; const url = `${getPathPrefix()}/workflow/${workflowId}`; - const { - data: dagResults, - error: dagResultsError, - isLoading: dagResultsLoading, - } = useDagResultsGetQuery({ - apiKey: user.apiKey, - workflowId: workflowId, - }); + const { latestDagResult, dag } = useLatestDagResultOrDag( + user.apiKey, + workflowId + ); let status = ExecutionStatus.Unknown; - if (!dagResultsLoading && !dagResultsError && dagResults.length > 0) { - const latestDagResult = getLatestDagResult(dagResults); - if (latestDagResult) { - status = latestDagResult.exec_state.status; - } + if (latestDagResult) { + status = latestDagResult.exec_state.status; + } else if (dag) { + status = ExecutionStatus.Registered; } - return ; }, 'Last Run': LastRunComponent, Engines: (row) => { const workflowId = row.id; - const { - data: dagResults, - error: dagResultsError, - isLoading: dagResultsLoading, - } = useDagResultsGetQuery({ - apiKey: user.apiKey, - workflowId: workflowId, - }); - - let latestDagId; - if (!dagResultsLoading && !dagResultsError && dagResults.length > 0) { - const latestDagResult = getLatestDagResult(dagResults); - latestDagId = latestDagResult.dag_id; - } + const { latestDagResult, dag: noRunDag } = useLatestDagResultOrDag( + user.apiKey, + workflowId + ); + + const latestDagId = latestDagResult?.dag_id ?? noRunDag?.id; + const { data: dag, error: dagError, @@ -181,15 +156,17 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { dagId: latestDagId, }, { - skip: dagResultsLoading && latestDagId, + skip: !latestDagId || noRunDag, } ); const nodes = useWorkflowNodes(user.apiKey, workflowId, latestDagId); let engines = ['Unknown']; - if (!dagLoading && !dagError && dag) { - const workflowDag = structuredClone(dag); + if (dag || noRunDag) { + const workflowDag = noRunDag + ? structuredClone(noRunDag) + : structuredClone(dag); workflowDag.operators = nodes.operators; engines = getWorkflowEngineTypes(workflowDag); } @@ -211,22 +188,13 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { Metrics: (row) => { const workflowId = row.id; - const { - data: dagResults, - error: dagResultsError, - isLoading: dagResultsLoading, - } = useDagResultsGetQuery({ - apiKey: user.apiKey, - workflowId: workflowId, - }); - - let latestDagResultId; - let latestDagId; - if (!dagResultsLoading && !dagResultsError && dagResults.length > 0) { - const latestDagResult = getLatestDagResult(dagResults); - latestDagResultId = latestDagResult.id; - latestDagId = latestDagResult.dag_id; - } + const { latestDagResult, dag } = useLatestDagResultOrDag( + user.apiKey, + workflowId + ); + + const latestDagResultId = latestDagResult?.id; + const latestDagId = latestDagResult?.dag_id ?? dag?.id; const nodes = useWorkflowNodes(user.apiKey, workflowId, latestDagId); const nodesResults = useWorkflowNodesResults( @@ -243,7 +211,9 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { metricId: op.id, name: op.name, value: nodesResults.artifacts[artifactId]?.content_serialized, - status: nodesResults.artifacts[artifactId]?.exec_state?.status, + status: + nodesResults.artifacts[artifactId]?.exec_state?.status ?? + ExecutionStatus.Registered, }; }); return ; @@ -251,22 +221,13 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { Checks: (row) => { const workflowId = row.id; - const { - data: dagResults, - error: dagResultsError, - isLoading: dagResultsLoading, - } = useDagResultsGetQuery({ - apiKey: user.apiKey, - workflowId: workflowId, - }); - - let latestDagResultId; - let latestDagId; - if (!dagResultsLoading && !dagResultsError && dagResults.length > 0) { - const latestDagResult = getLatestDagResult(dagResults); - latestDagResultId = latestDagResult.id; - latestDagId = latestDagResult.dag_id; - } + const { latestDagResult, dag } = useLatestDagResultOrDag( + user.apiKey, + workflowId + ); + + const latestDagResultId = latestDagResult?.id; + const latestDagId = latestDagResult?.dag_id ?? dag?.id; const nodes = useWorkflowNodes(user.apiKey, workflowId, latestDagId); const nodesResults = useWorkflowNodesResults( @@ -282,7 +243,9 @@ const WorkflowsPage: React.FC = ({ user, Layout = DefaultLayout }) => { return { checkId: op.id, name: op.name, - status: nodesResults.artifacts[artifactId]?.exec_state?.status, + status: + nodesResults.artifacts[artifactId]?.exec_state?.status ?? + ExecutionStatus.Registered, level: op.spec.check.level, value: nodesResults.artifacts[artifactId]?.content_serialized, timestamp: diff --git a/src/ui/common/src/components/tables/PaginatedSearchTable.tsx b/src/ui/common/src/components/tables/PaginatedSearchTable.tsx index 2eb695477..176038cfa 100644 --- a/src/ui/common/src/components/tables/PaginatedSearchTable.tsx +++ b/src/ui/common/src/components/tables/PaginatedSearchTable.tsx @@ -40,7 +40,7 @@ export type SortColumn = { // The sequence of keys in the row object to access in order to get the // value which should be compared for sort purposes. - sortAccessPath: string[]; + sortAccessPath: (string | number)[]; }; export enum SortType { @@ -50,7 +50,7 @@ export enum SortType { } type SortConfig = { - name: SortColumn; + sortColumn: SortColumn; sortType: SortType; }; @@ -75,6 +75,7 @@ export const PaginatedSearchTable: React.FC = ({ onChangeRowsPerPage, savedRowsPerPage, sortColumns = [], + defaultSortConfig, }) => { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState( @@ -89,7 +90,7 @@ export const PaginatedSearchTable: React.FC = ({ const [sortTypeMenuAnchor, setSortTypeMenuAnchor] = useState(null); const [sortConfig, setSortConfig] = useState({ - sortColumn: { name: null, sortAccessPath: [] as string[] }, + sortColumn: null, sortType: SortType.None, }); @@ -105,7 +106,7 @@ export const PaginatedSearchTable: React.FC = ({ return value; }; - const rowData = [...data].map((row) => { + let rowData = [...data].map((row) => { const rowData = {}; columns.forEach((column) => { rowData[column] = getColumnValue(row, column); @@ -113,6 +114,38 @@ export const PaginatedSearchTable: React.FC = ({ return rowData; }); + // Default ordering + if (defaultSortConfig) { + rowData = rowData.sort((r1, r2) => { + const col = defaultSortConfig.sortColumn; + let v1: PaginatedSearchTableRow | PaginatedSearchTableElement = r1; + let v2: PaginatedSearchTableRow | PaginatedSearchTableElement = r2; + for (const path of col.sortAccessPath) { + v1 = v1[path]; + v2 = v2[path]; + } + + if (defaultSortConfig.sortType === SortType.Ascending) { + if (v1 > v2) { + return 1; + } else if (v1 < v2) { + return -1; + } else { + return 0; + } + } else { + // sortType === SortType.Descending + if (v1 > v2) { + return -1; + } else if (v1 < v2) { + return 1; + } else { + return 0; + } + } + }); + } + const [rows, setRows] = useState(rowData); const [orderedRows, setOrderedRows] = useState(rowData); diff --git a/src/ui/common/src/utils/shared.ts b/src/ui/common/src/utils/shared.ts index 8c9c94ea1..e690c159b 100644 --- a/src/ui/common/src/utils/shared.ts +++ b/src/ui/common/src/utils/shared.ts @@ -1,4 +1,5 @@ import { ArtifactResultResponse } from '../handlers/responses/node'; +import { DagResultResponse } from '../handlers/responses/workflow'; import { TableRow } from './data'; export enum AWSCredentialType { @@ -74,6 +75,21 @@ export const getArtifactResultTableRow = ( }; }; +export function getLatestDagResult( + dagResults: DagResultResponse[] +): DagResultResponse { + if (dagResults && dagResults.length > 0) { + return dagResults.reduce((prev, curr) => + curr.exec_state?.timestamps?.pending_at + ? new Date(prev.exec_state?.timestamps?.pending_at) < + new Date(curr.exec_state?.timestamps?.pending_at) + ? curr + : prev + : curr + ); + } +} + export const stringToExecutionStatus = (status: string): ExecutionStatus => { let executionStatus = ExecutionStatus.Unknown; switch (status) {