diff --git a/packages/zapp/console/src/components/Entities/EntityInputs.tsx b/packages/zapp/console/src/components/Entities/EntityInputs.tsx index ede82f6dd..4b85a2047 100644 --- a/packages/zapp/console/src/components/Entities/EntityInputs.tsx +++ b/packages/zapp/console/src/components/Entities/EntityInputs.tsx @@ -16,6 +16,7 @@ import { FilterOperationName } from 'models/AdminEntity/types'; import { ResourceIdentifier } from 'models/Common/types'; import { LaunchPlanClosure, LaunchPlanSpec } from 'models/Launch/types'; import * as React from 'react'; +import { useMemo } from 'react'; import t from './strings'; import { transformLiterals } from '../Literals/helpers'; @@ -104,8 +105,8 @@ export const EntityInputs: React.FC<{ ? launchPlanState.value[0].spec : ({} as LaunchPlanSpec); - const expectedInputs = React.useMemo(() => { - const results = [] as Input[]; + const expectedInputs = useMemo(() => { + const results: Input[] = []; Object.keys(closure?.expectedInputs?.parameters ?? {}).forEach((name) => { const parameter = closure?.expectedInputs.parameters[name]; if (parameter?.var?.type) { @@ -121,25 +122,11 @@ export const EntityInputs: React.FC<{ return results; }, [closure]); - const fixedInputs = React.useMemo(() => { + const fixedInputs = useMemo(() => { const inputsMap = transformLiterals(spec?.fixedInputs?.literals ?? {}); return Object.keys(inputsMap).map((name) => ({ name, defaultValue: inputsMap[name] })); }, [spec]); - const configs = React.useMemo( - () => [ - { name: t('configType'), value: 'single (csv)' }, - { - name: t('configUrl'), - value: - 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv', - }, - { name: t('configSeed'), value: '7' }, - { name: t('configTestSplitRatio'), value: '0.33' }, - ], - [], - ); - return ( <> @@ -232,22 +219,6 @@ export const EntityInputs: React.FC<{ )} - {/*
-
- - {t('configuration')} - -
    - {configs.map(({ name, value }) => ( -
  • - {name}: - {value} -
  • - ))} -
-
-
-
*/} ); }; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeViews.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeViews.tsx index f7f51f694..8cf4f29f7 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeViews.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeViews.tsx @@ -6,13 +6,14 @@ import { DataError } from 'components/Errors/DataError'; import { useTabState } from 'components/hooks/useTabState'; import { secondaryBackgroundColor } from 'components/Theme/constants'; import { Execution, ExternalResource, LogsByPhase, NodeExecution } from 'models/Execution/types'; -import { useContext, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { keyBy } from 'lodash'; import { isMapTaskV1 } from 'models/Task/utils'; import { useQueryClient } from 'react-query'; import { LargeLoadingSpinner } from 'components/common/LoadingSpinner'; +import { FilterOperation } from 'models/AdminEntity/types'; import { NodeExecutionDetailsContextProvider } from '../contextProvider/NodeExecutionDetails'; -import { NodeExecutionsByIdContext, NodeExecutionsRequestConfigContext } from '../contexts'; +import { NodeExecutionsByIdContext } from '../contexts'; import { ExecutionFilters } from '../ExecutionFilters'; import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState'; import { tabs } from './constants'; @@ -42,6 +43,13 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })); +const isPhaseFilter = (appliedFilters: FilterOperation[]) => { + if (appliedFilters.length === 1 && appliedFilters[0].key === 'phase') { + return true; + } + return false; +}; + interface WorkflowNodeExecution extends NodeExecution { logsByPhase?: LogsByPhase; } @@ -57,7 +65,6 @@ export const ExecutionNodeViews: React.FC = ({ executio const filterState = useNodeExecutionFiltersState(); const tabState = useTabState(tabs, defaultTab); const queryClient = useQueryClient(); - const requestConfig = useContext(NodeExecutionsRequestConfigContext); // Can't find initialization of the provider const [nodeExecutionsLoading, setNodeExecutionsLoading] = useState(true); const { @@ -73,12 +80,12 @@ export const ExecutionNodeViews: React.FC = ({ executio return keyBy(nodeExecutionsWithResources, 'scopedId'); }, [nodeExecutionsWithResources]); - /* We want to maintain the filter selection when switching away from the Nodes - tab and back, but do not want to filter the nodes when viewing the graph. So, - we will only pass filters to the execution state when on the nodes tab. */ - const appliedFilters = tabState.value === tabs.nodes.id ? filterState.appliedFilters : []; - - const { nodeExecutionsQuery } = useExecutionNodeViewsState(execution, appliedFilters); + // query to get all data to build Graph and Timeline + const { nodeExecutionsQuery } = useExecutionNodeViewsState(execution); + // query to get filtered data to narrow down Table outputs + const { + nodeExecutionsQuery: { data: filteredNodeExecutions }, + } = useExecutionNodeViewsState(execution, filterState.appliedFilters); useEffect(() => { let isCurrent = true; @@ -131,10 +138,7 @@ export const ExecutionNodeViews: React.FC = ({ executio }; }, [nodeExecutions]); - const childGroupsQuery = useAllTreeNodeExecutionGroupsQuery( - nodeExecutionsQuery.data ?? [], - requestConfig, - ); + const childGroupsQuery = useAllTreeNodeExecutionGroupsQuery(nodeExecutionsQuery.data ?? [], {}); useEffect(() => { if (!childGroupsQuery.isLoading && childGroupsQuery.data) { @@ -161,7 +165,13 @@ export const ExecutionNodeViews: React.FC = ({ executio loadingComponent={LoadingComponent} > {() => ( - + )} ); diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTab.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTab.tsx index 5f6eb55e7..c5dd4a50b 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTab.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTab.tsx @@ -11,7 +11,7 @@ import { ExecutionTabContent } from './ExecutionTabContent'; export interface ExecutionTabProps { tabType: string; - filteredNodeExecutions: NodeExecution[]; + filteredNodeExecutions?: NodeExecution[]; } /** Contains the available ways to visualize the nodes of a WorkflowExecution */ diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTabContent.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTabContent.tsx index b5029e0b2..db69cd778 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTabContent.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionTabContent.tsx @@ -11,6 +11,12 @@ import { checkForDynamicExecutions } from 'components/common/utils'; import { dNode } from 'models/Graph/types'; import { useContext, useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; +import { + FilterOperation, + FilterOperationName, + FilterOperationValueList, +} from 'models/AdminEntity/types'; +import { isEqual } from 'lodash'; import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails'; import { NodeExecutionsByIdContext } from '../contexts'; import { NodeExecutionsTable } from '../Tables/NodeExecutionsTable'; @@ -20,10 +26,12 @@ import { ExecutionTimeline } from './Timeline/ExecutionTimeline'; import { ExecutionTimelineFooter } from './Timeline/ExecutionTimelineFooter'; import { convertToPlainNodes, TimeZone } from './Timeline/helpers'; import { DetailsPanelContext } from './DetailsPanelContext'; +import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState'; +import { nodeExecutionPhaseConstants } from '../constants'; export interface ExecutionTabContentProps { tabType: string; - filteredNodeExecutions: NodeExecution[]; + filteredNodeExecutions?: NodeExecution[]; } const useStyles = makeStyles(() => ({ @@ -39,29 +47,50 @@ const useStyles = makeStyles(() => ({ }, })); +const executionMatchesPhaseFilter = ( + nodeExecution: NodeExecution, + { key, value, operation }: FilterOperation, +) => { + if (key === 'phase' && operation === FilterOperationName.VALUE_IN) { + // default to UNKNOWN phase if the field does not exist on a closure + const itemValue = + nodeExecutionPhaseConstants[nodeExecution?.closure[key]]?.value ?? + nodeExecutionPhaseConstants[0].value; + // phase check filters always return values in an array + const valuesArray = value as FilterOperationValueList; + return valuesArray.includes(itemValue); + } + return false; +}; + export const ExecutionTabContent: React.FC = ({ tabType, filteredNodeExecutions, }) => { const styles = useStyles(); const { compiledWorkflowClosure } = useNodeExecutionContext(); + const { appliedFilters } = useNodeExecutionFiltersState(); + const nodeExecutionsById = useContext(NodeExecutionsByIdContext); + const { dag, staticExecutionIdsMap, error } = compiledWorkflowClosure ? transformerWorkflowToDag(compiledWorkflowClosure) : { dag: {}, staticExecutionIdsMap: {}, error: null }; - const nodeExecutionsById = useContext(NodeExecutionsByIdContext); const dynamicParents = checkForDynamicExecutions(nodeExecutionsById, staticExecutionIdsMap); const { data: dynamicWorkflows } = useQuery( makeNodeExecutionDynamicWorkflowQuery(dynamicParents), ); const [initialNodes, setInitialNodes] = useState([]); + const [initialFilteredNodes, setInitialFilteredNodes] = useState(undefined); const [mergedDag, setMergedDag] = useState(null); + const [filters, setFilters] = useState(appliedFilters); + const [isFiltersChanged, setIsFiltersChanged] = useState(false); useEffect(() => { const nodes: dNode[] = compiledWorkflowClosure ? transformerWorkflowToDag(compiledWorkflowClosure, dynamicWorkflows).dag.nodes : []; // we remove start/end node info in the root dNode list during first assignment - const initialNodes = convertToPlainNodes(nodes); + const plainNodes = convertToPlainNodes(nodes); let newMergedDag = dag; @@ -77,9 +106,38 @@ export const ExecutionTabContent: React.FC = ({ } } setMergedDag(newMergedDag); - setInitialNodes(initialNodes); + setInitialNodes(plainNodes); }, [compiledWorkflowClosure, dynamicWorkflows]); + useEffect(() => { + if (!isEqual(filters, appliedFilters)) { + setFilters(appliedFilters); + setIsFiltersChanged(true); + } else { + setIsFiltersChanged(false); + } + }, [appliedFilters]); + + useEffect(() => { + if (appliedFilters.length > 0) { + // if filter was apllied, but filteredNodeExecutions is empty, we only appliied Phase filter, + // and need to clear out items manually + if (!filteredNodeExecutions) { + const filteredNodes = initialNodes.filter((node) => + executionMatchesPhaseFilter(nodeExecutionsById[node.scopedId], appliedFilters[0]), + ); + setInitialFilteredNodes(filteredNodes); + } else { + const filteredNodes = initialNodes.filter((node: dNode) => + filteredNodeExecutions.find( + (execution: NodeExecution) => execution.scopedId === node.scopedId, + ), + ); + setInitialFilteredNodes(filteredNodes); + } + } + }, [initialNodes, filteredNodeExecutions, isFiltersChanged]); + const [selectedNodes, setSelectedNodes] = useState([]); // Note: flytegraph allows multiple selection, but we only support showing @@ -140,10 +198,7 @@ export const ExecutionTabContent: React.FC = ({ switch (tabType) { case tabs.nodes.id: return ( - + ); case tabs.graph.id: return ( diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/test/ExecutionNodeViews.test.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/test/ExecutionNodeViews.test.tsx index d8a5a683f..76c24e3d9 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/test/ExecutionNodeViews.test.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/test/ExecutionNodeViews.test.tsx @@ -11,24 +11,26 @@ import { createTestQueryClient } from 'test/utils'; import { tabs } from '../constants'; import { ExecutionNodeViews } from '../ExecutionNodeViews'; -jest.mock('chart.js', () => ({ - Chart: { register: () => null }, - Tooltip: { positioners: { cursor: () => null } }, - registerables: [], -})); - -jest.mock('chartjs-plugin-datalabels', () => ({ - ChartDataLabels: null, -})); - jest.mock('components/Executions/Tables/NodeExecutionRow', () => ({ - NodeExecutionRow: jest.fn(({ children, execution }) => ( + NodeExecutionRow: jest.fn(({ nodeExecution }) => (
- {execution?.id?.nodeId} - {children} + {nodeExecution?.id?.nodeId}
)), })); + +jest.mock('components/Executions/ExecutionDetails/Timeline/ExecutionTimelineFooter', () => ({ + ExecutionTimelineFooter: jest.fn(() =>
), +})); + +jest.mock('components/Executions/ExecutionDetails/Timeline/TimelineChart/index', () => ({ + TimelineChart: jest.fn(() =>
), +})); + +jest.mock('components/Executions/ExecutionDetails/Timeline/NodeExecutionName', () => ({ + NodeExecutionName: jest.fn(({ name }) =>
{name}
), +})); + // ExecutionNodeViews uses query params for NE list, so we must match them // for the list to be returned properly const baseQueryParams = { @@ -77,7 +79,7 @@ describe('ExecutionNodeViews', () => { await waitFor(() => getByText(tabs.nodes.label)); const nodesTab = getByText(tabs.nodes.label); - const graphTab = getByText(tabs.graph.label); + const timelineTab = getByText(tabs.timeline.label); // Ensure we are on Nodes tab fireEvent.click(nodesTab); @@ -96,11 +98,11 @@ describe('ExecutionNodeViews', () => { await waitFor(() => queryByText(failedNodeName)); expect(queryByText(succeededNodeName)).not.toBeInTheDocument(); - expect(getByText(failedNodeName)).toBeInTheDocument(); + expect(queryByText(failedNodeName)).toBeInTheDocument(); // Switch to the Graph tab fireEvent.click(statusButton); - fireEvent.click(graphTab); + fireEvent.click(timelineTab); await waitFor(() => queryByText(succeededNodeName)); expect(queryByText(succeededNodeName)).toBeInTheDocument(); diff --git a/packages/zapp/console/src/components/Executions/ExecutionFilters.tsx b/packages/zapp/console/src/components/Executions/ExecutionFilters.tsx index 2b4ce8d5c..a93ad844b 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionFilters.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionFilters.tsx @@ -79,8 +79,12 @@ export const ExecutionFilters: React.FC = ({ filters = filters.map((filter) => { const onChangeFunc = filter.onChange; filter.onChange = (value) => { - if (clearCharts) clearCharts(); - if (onChangeFunc) onChangeFunc(value); + if (clearCharts) { + clearCharts(); + } + if (onChangeFunc) { + onChangeFunc(value); + } }; return filter; }); diff --git a/packages/zapp/console/src/components/Executions/Tables/NodeExecutionsTable.tsx b/packages/zapp/console/src/components/Executions/Tables/NodeExecutionsTable.tsx index 0979b0b33..d32c2104a 100644 --- a/packages/zapp/console/src/components/Executions/Tables/NodeExecutionsTable.tsx +++ b/packages/zapp/console/src/components/Executions/Tables/NodeExecutionsTable.tsx @@ -15,13 +15,13 @@ import { NoExecutionsContent } from './NoExecutionsContent'; import { useColumnStyles, useExecutionTableStyles } from './styles'; import { NodeExecutionsByIdContext } from '../contexts'; import { convertToPlainNodes } from '../ExecutionDetails/Timeline/helpers'; -// import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState'; import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails'; import { NodeExecutionRow } from './NodeExecutionRow'; +import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState'; export interface NodeExecutionsTableProps { initialNodes: dNode[]; - filteredNodeExecutions: NodeExecution[]; + filteredNodes?: dNode[]; } const scrollbarPadding = scrollbarSize(); @@ -36,13 +36,18 @@ const scrollbarPadding = scrollbarSize(); * NodeExecutions are expandable and will potentially render a list of child * TaskExecutions */ -export const NodeExecutionsTable: React.FC = ({ initialNodes }) => { +export const NodeExecutionsTable: React.FC = ({ + initialNodes, + filteredNodes, +}) => { const commonStyles = useCommonStyles(); const tableStyles = useExecutionTableStyles(); const nodeExecutionsById = useContext(NodeExecutionsByIdContext); - const [originalNodes, setOriginalNodes] = useState(initialNodes); + const { appliedFilters } = useNodeExecutionFiltersState(); + const [originalNodes, setOriginalNodes] = useState( + appliedFilters.length > 0 && filteredNodes ? filteredNodes : initialNodes, + ); const [showNodes, setShowNodes] = useState([]); - // const filterState = useNodeExecutionFiltersState(); const { compiledWorkflowClosure } = useNodeExecutionContext(); const columnStyles = useColumnStyles(); @@ -53,7 +58,7 @@ export const NodeExecutionsTable: React.FC = ({ initia ); useEffect(() => { - setOriginalNodes(initialNodes); + setOriginalNodes(appliedFilters.length > 0 && filteredNodes ? filteredNodes : initialNodes); const plainNodes = convertToPlainNodes(originalNodes); const updatedShownNodesMap = plainNodes.map((node) => { const execution = nodeExecutionsById[node.scopedId]; @@ -64,7 +69,7 @@ export const NodeExecutionsTable: React.FC = ({ initia }; }); setShowNodes(updatedShownNodesMap); - }, [initialNodes, originalNodes, nodeExecutionsById]); + }, [initialNodes, filteredNodes, originalNodes, nodeExecutionsById]); const toggleNode = (id: string, scopeId: string, level: number) => { const searchNode = (nodes: dNode[], nodeLevel: number) => { diff --git a/packages/zapp/console/src/components/Executions/Tables/test/NodeExecutionsTable.test.tsx b/packages/zapp/console/src/components/Executions/Tables/test/NodeExecutionsTable.test.tsx index 4b7f06a32..e9600a9c3 100644 --- a/packages/zapp/console/src/components/Executions/Tables/test/NodeExecutionsTable.test.tsx +++ b/packages/zapp/console/src/components/Executions/Tables/test/NodeExecutionsTable.test.tsx @@ -1,27 +1,27 @@ import { render, waitFor } from '@testing-library/react'; import { NodeExecutionDetailsContextProvider } from 'components/Executions/contextProvider/NodeExecutionDetails'; -import { - NodeExecutionsByIdContext, - NodeExecutionsRequestConfigContext, -} from 'components/Executions/contexts'; +import { NodeExecutionsByIdContext } from 'components/Executions/contexts'; import { basicPythonWorkflow } from 'mocks/data/fixtures/basicPythonWorkflow'; import { noExecutionsFoundString } from 'common/constants'; import { mockWorkflowId } from 'mocks/data/fixtures/types'; import { insertFixture } from 'mocks/data/insertFixture'; import { mockServer } from 'mocks/server'; -import { RequestConfig } from 'models/AdminEntity/types'; import { NodeExecutionPhase } from 'models/Execution/enums'; import * as React from 'react'; import { dateToTimestamp } from 'common/utils'; import { QueryClient, QueryClientProvider } from 'react-query'; import { createTestQueryClient } from 'test/utils'; -import { NodeExecution } from 'models/Execution/types'; import { dNode } from 'models/Graph/types'; +import { useNodeExecutionFiltersState } from 'components/Executions/filters/useExecutionFiltersState'; import { NodeExecutionsTable } from '../NodeExecutionsTable'; jest.mock('components/Workflow/workflowQueries'); const { fetchWorkflow } = require('components/Workflow/workflowQueries'); +jest.mock('components/Executions/filters/useExecutionFiltersState'); +const mockUseNodeExecutionFiltersState = useNodeExecutionFiltersState as jest.Mock; +mockUseNodeExecutionFiltersState.mockReturnValue({ filters: [], appliedFilters: [] }); + jest.mock('components/Executions/Tables/NodeExecutionRow', () => ({ NodeExecutionRow: jest.fn(({ nodeExecution }) => (
@@ -46,26 +46,6 @@ const mockNodes = (n: number): dNode[] => { return nodes; }; -const mockNodeExecutions = (n: number, phases: NodeExecutionPhase[]): NodeExecution[] => { - const nodeExecutions: NodeExecution[] = []; - for (let i = 1; i <= n; i++) { - nodeExecutions.push({ - closure: { - createdAt: dateToTimestamp(new Date()), - outputUri: '', - phase: phases[i - 1], - }, - id: { - executionId: { domain: 'domain', name: 'name', project: 'project' }, - nodeId: `node${i}`, - }, - inputUri: '', - scopedId: `n${i}`, - }); - } - return nodeExecutions; -}; - const mockExecutionsById = (n: number, phases: NodeExecutionPhase[]) => { const nodeExecutionsById = {}; @@ -89,31 +69,24 @@ const mockExecutionsById = (n: number, phases: NodeExecutionPhase[]) => { describe('NodeExecutionsTableExecutions > Tables > NodeExecutionsTable', () => { let queryClient: QueryClient; - let requestConfig: RequestConfig; let fixture: ReturnType; const initialNodes = mockNodes(2); beforeEach(() => { - requestConfig = {}; queryClient = createTestQueryClient(); fixture = basicPythonWorkflow.generate(); insertFixture(mockServer, fixture); fetchWorkflow.mockImplementation(() => Promise.resolve(fixture.workflows.top)); }); - const renderTable = ({ nodeExecutionsById, initialNodes, filteredNodeExecutions }) => + const renderTable = ({ nodeExecutionsById, initialNodes, filteredNodes }) => render( - - - - - - - + + + + + , ); @@ -121,7 +94,7 @@ describe('NodeExecutionsTableExecutions > Tables > NodeExecutionsTable', () => { const { queryByText, queryByTestId } = renderTable({ initialNodes: [], nodeExecutionsById: {}, - filteredNodeExecutions: [], + filteredNodes: [], }); await waitFor(() => queryByText(noExecutionsFoundString)); @@ -130,15 +103,14 @@ describe('NodeExecutionsTableExecutions > Tables > NodeExecutionsTable', () => { expect(queryByTestId('node-execution-row')).not.toBeInTheDocument(); }); - it('renders NodeExecutionRows with proper nodeExecutions', async () => { + it('renders NodeExecutionRows with initialNodes when no filteredNodes were provided', async () => { const phases = [NodeExecutionPhase.FAILED, NodeExecutionPhase.SUCCEEDED]; const nodeExecutionsById = mockExecutionsById(2, phases); - const filteredNodeExecutions = mockNodeExecutions(2, phases); const { queryAllByTestId } = renderTable({ initialNodes, nodeExecutionsById, - filteredNodeExecutions, + filteredNodes: undefined, }); await waitFor(() => queryAllByTestId('node-execution-row')); @@ -154,15 +126,15 @@ describe('NodeExecutionsTableExecutions > Tables > NodeExecutionsTable', () => { } }); - it('renders future nodes with UNDEFINED phase', async () => { - const phases = [NodeExecutionPhase.SUCCEEDED, NodeExecutionPhase.UNDEFINED]; - const nodeExecutionsById = mockExecutionsById(1, phases); - const filteredNodeExecutions = mockNodeExecutions(1, phases); + it('renders NodeExecutionRows with initialNodes even when filterNodes were provided, if appliedFilters is empty', async () => { + const phases = [NodeExecutionPhase.FAILED, NodeExecutionPhase.SUCCEEDED]; + const nodeExecutionsById = mockExecutionsById(2, phases); + const filteredNodes = mockNodes(1); const { queryAllByTestId } = renderTable({ initialNodes, nodeExecutionsById, - filteredNodeExecutions, + filteredNodes, }); await waitFor(() => queryAllByTestId('node-execution-row')); @@ -177,4 +149,33 @@ describe('NodeExecutionsTableExecutions > Tables > NodeExecutionsTable', () => { expect(renderedPhases[i]).toHaveTextContent(phases[i].toString()); } }); + + it('renders NodeExecutionRows with filterNodes if appliedFilters is not empty', async () => { + mockUseNodeExecutionFiltersState.mockReturnValueOnce({ + filters: [], + appliedFilters: [{ key: 'phase', operation: 'value_in', value: ['FAILED', 'SUCCEEDED'] }], + }); + + const phases = [NodeExecutionPhase.FAILED, NodeExecutionPhase.SUCCEEDED]; + const nodeExecutionsById = mockExecutionsById(2, phases); + const filteredNodes = mockNodes(1); + + const { queryAllByTestId } = renderTable({ + initialNodes, + nodeExecutionsById, + filteredNodes, + }); + + await waitFor(() => queryAllByTestId('node-execution-row')); + + expect(queryAllByTestId('node-execution-row')).toHaveLength(filteredNodes.length); + const ids = queryAllByTestId('node-execution-col-id'); + expect(ids).toHaveLength(filteredNodes.length); + const renderedPhases = queryAllByTestId('node-execution-col-phase'); + expect(renderedPhases).toHaveLength(filteredNodes.length); + for (const i in filteredNodes) { + expect(ids[i]).toHaveTextContent(filteredNodes[i].id); + expect(renderedPhases[i]).toHaveTextContent(phases[i].toString()); + } + }); }); diff --git a/packages/zapp/console/src/components/Executions/constants.ts b/packages/zapp/console/src/components/Executions/constants.ts index ded16e059..4a24e391a 100644 --- a/packages/zapp/console/src/components/Executions/constants.ts +++ b/packages/zapp/console/src/components/Executions/constants.ts @@ -24,60 +24,70 @@ export const workflowExecutionPhaseConstants: { } = { [WorkflowExecutionPhase.ABORTED]: { text: t('aborted'), + value: 'ABORTED', badgeColor: statusColors.SKIPPED, nodeColor: graphStatusColors.ABORTED, textColor: negativeTextColor, }, [WorkflowExecutionPhase.ABORTING]: { text: t('aborting'), + value: 'ABORTING', badgeColor: statusColors.SKIPPED, nodeColor: graphStatusColors.ABORTED, textColor: negativeTextColor, }, [WorkflowExecutionPhase.FAILING]: { text: t('failing'), + value: 'FAILING', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILING, textColor: negativeTextColor, }, [WorkflowExecutionPhase.FAILED]: { text: t('failed'), + value: 'FAILED', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILED, textColor: negativeTextColor, }, [WorkflowExecutionPhase.QUEUED]: { text: t('queued'), + value: 'QUEUED', badgeColor: statusColors.QUEUED, nodeColor: graphStatusColors.QUEUED, textColor: secondaryTextColor, }, [WorkflowExecutionPhase.RUNNING]: { text: t('running'), + value: 'RUNNING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [WorkflowExecutionPhase.SUCCEEDED]: { text: t('succeeded'), + value: 'SUCCEEDED', badgeColor: statusColors.SUCCESS, nodeColor: graphStatusColors.SUCCEEDED, textColor: positiveTextColor, }, [WorkflowExecutionPhase.SUCCEEDING]: { text: t('succeeding'), + value: 'SUCCEEDING', badgeColor: statusColors.SUCCESS, nodeColor: graphStatusColors.SUCCEEDED, textColor: positiveTextColor, }, [WorkflowExecutionPhase.TIMED_OUT]: { text: t('timedOut'), + value: 'TIMED_OUT', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILED, textColor: negativeTextColor, }, [WorkflowExecutionPhase.UNDEFINED]: { text: t('unknown'), + value: 'UNKNOWN', badgeColor: statusColors.UNKNOWN, nodeColor: graphStatusColors.UNDEFINED, textColor: secondaryTextColor, @@ -90,72 +100,84 @@ export const nodeExecutionPhaseConstants: { } = { [NodeExecutionPhase.ABORTED]: { text: t('aborted'), + value: 'ABORTED', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.ABORTED, textColor: negativeTextColor, }, [NodeExecutionPhase.FAILING]: { text: t('failing'), + value: 'FAILING', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILING, textColor: negativeTextColor, }, [NodeExecutionPhase.FAILED]: { text: t('failed'), + value: 'FAILED', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILED, textColor: negativeTextColor, }, [NodeExecutionPhase.QUEUED]: { text: t('queued'), + value: 'QUEUED', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.QUEUED, textColor: secondaryTextColor, }, [NodeExecutionPhase.RUNNING]: { text: t('running'), + value: 'RUNNING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [NodeExecutionPhase.DYNAMIC_RUNNING]: { text: t('running'), + value: 'RUNNING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [NodeExecutionPhase.SUCCEEDED]: { text: t('succeeded'), + value: 'SUCCEEDED', badgeColor: statusColors.SUCCESS, nodeColor: graphStatusColors.SUCCEEDED, textColor: positiveTextColor, }, [NodeExecutionPhase.TIMED_OUT]: { text: t('timedOut'), + value: 'TIMED_OUT', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILED, textColor: negativeTextColor, }, [NodeExecutionPhase.SKIPPED]: { text: t('skipped'), + value: 'SKIPPED', badgeColor: statusColors.UNKNOWN, nodeColor: graphStatusColors.UNDEFINED, textColor: secondaryTextColor, }, [NodeExecutionPhase.RECOVERED]: { text: t('recovered'), + value: 'RECOVERED', badgeColor: statusColors.SUCCESS, nodeColor: graphStatusColors.SUCCEEDED, textColor: positiveTextColor, }, [NodeExecutionPhase.PAUSED]: { text: t('paused'), + value: 'PAUSED', badgeColor: statusColors.PAUSED, nodeColor: graphStatusColors.PAUSED, textColor: secondaryTextColor, }, [NodeExecutionPhase.UNDEFINED]: { text: t('unknown'), + value: 'UNKNOWN', badgeColor: statusColors.UNKNOWN, nodeColor: graphStatusColors.UNDEFINED, textColor: secondaryTextColor, @@ -168,48 +190,56 @@ export const taskExecutionPhaseConstants: { } = { [TaskExecutionPhase.ABORTED]: { text: t('aborted'), + value: 'ABORTED', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.ABORTED, textColor: negativeTextColor, }, [TaskExecutionPhase.FAILED]: { text: t('failed'), + value: 'FAILED', badgeColor: statusColors.FAILURE, nodeColor: graphStatusColors.FAILED, textColor: negativeTextColor, }, [TaskExecutionPhase.WAITING_FOR_RESOURCES]: { text: t('waiting'), + value: 'WAITING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [TaskExecutionPhase.QUEUED]: { text: t('queued'), + value: 'QUEUED', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.QUEUED, textColor: secondaryTextColor, }, [TaskExecutionPhase.INITIALIZING]: { text: t('initializing'), + value: 'INITIALIZING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [TaskExecutionPhase.RUNNING]: { text: t('running'), + value: 'RUNNING', badgeColor: statusColors.RUNNING, nodeColor: graphStatusColors.RUNNING, textColor: secondaryTextColor, }, [TaskExecutionPhase.SUCCEEDED]: { text: t('succeeded'), + value: 'SUCCEEDED', badgeColor: statusColors.SUCCESS, nodeColor: graphStatusColors.SUCCEEDED, textColor: positiveTextColor, }, [TaskExecutionPhase.UNDEFINED]: { text: t('unknown'), + value: 'UNKNOWN', badgeColor: statusColors.UNKNOWN, nodeColor: graphStatusColors.UNDEFINED, textColor: secondaryTextColor, diff --git a/packages/zapp/console/src/components/Executions/contexts.ts b/packages/zapp/console/src/components/Executions/contexts.ts index faeb47b6d..e5ca0d492 100644 --- a/packages/zapp/console/src/components/Executions/contexts.ts +++ b/packages/zapp/console/src/components/Executions/contexts.ts @@ -1,15 +1,10 @@ -import { RequestConfig } from 'models/AdminEntity/types'; import { Execution, NodeExecution } from 'models/Execution/types'; -import * as React from 'react'; +import { createContext } from 'react'; export interface ExecutionContextData { execution: Execution; } -export const ExecutionContext = React.createContext( - {} as ExecutionContextData, -); +export const ExecutionContext = createContext({} as ExecutionContextData); -export const NodeExecutionsByIdContext = React.createContext>({}); - -export const NodeExecutionsRequestConfigContext = React.createContext({}); +export const NodeExecutionsByIdContext = createContext>({}); diff --git a/packages/zapp/console/src/components/Executions/filters/startTimeFilters.ts b/packages/zapp/console/src/components/Executions/filters/startTimeFilters.ts index bd268a7b8..16847621e 100644 --- a/packages/zapp/console/src/components/Executions/filters/startTimeFilters.ts +++ b/packages/zapp/console/src/components/Executions/filters/startTimeFilters.ts @@ -2,8 +2,8 @@ import * as moment from 'moment'; import { FilterOperationName } from 'models/AdminEntity/types'; import { FilterMap } from './types'; -const workflowExecutionStartTimeKey = 'execution_created_at'; -const nodeExecutionStartTimeKey = 'node_execution_created_at'; +const workflowExecutionStartTimeKey = 'created_at'; +const nodeExecutionStartTimeKey = 'created_at'; export type StartTimeFilterKey = | 'all' diff --git a/packages/zapp/console/src/components/Executions/filters/statusFilters.ts b/packages/zapp/console/src/components/Executions/filters/statusFilters.ts index 9cddf0150..7b2293820 100644 --- a/packages/zapp/console/src/components/Executions/filters/statusFilters.ts +++ b/packages/zapp/console/src/components/Executions/filters/statusFilters.ts @@ -1,3 +1,7 @@ +import { + nodeExecutionPhaseConstants, + workflowExecutionPhaseConstants, +} from 'components/Executions/constants'; import { NodeExecutionPhase, WorkflowExecutionPhase } from 'models/Execution/enums'; import { FilterMap } from './types'; @@ -15,32 +19,32 @@ export const workflowExecutionStatusFilters: FilterMap; export type ExecutionError = RequiredNonNullable; @@ -104,7 +99,7 @@ export interface NodeExecutionClosure extends Admin.INodeExecutionClosure { duration?: Protobuf.Duration; error?: ExecutionError; outputUri: string; - phase: NodeExecutionPhase; + phase: Core.NodeExecution.Phase; startedAt?: Protobuf.ITimestamp; taskNodeMetadata?: TaskNodeMetadata; workflowNodeMetadata?: WorkflowNodeMetadata;