Skip to content

Commit

Permalink
chore: [tlm] comprehensive node execution query (#749)
Browse files Browse the repository at this point in the history
* chore: comprehensive node execution query

Signed-off-by: Carina Ursu <[email protected]>

* chore: fix graph render bug

Signed-off-by: Carina Ursu <[email protected]>

* chore: test fixes

Signed-off-by: Carina Ursu <[email protected]>

* chore: additions

Signed-off-by: Carina Ursu <[email protected]>

* chore: build fix?

Signed-off-by: Carina Ursu <[email protected]>

---------

Signed-off-by: Carina Ursu <[email protected]>
  • Loading branch information
ursucarina authored Apr 28, 2023
1 parent 957f9fa commit 8f8d686
Show file tree
Hide file tree
Showing 38 changed files with 377 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
node-version: 18
cache: yarn
- name: Install deps
run: yarn install --frozen-lockfile
run: yarn install --immutable
- name: Run tests and generate coverage
run: make test_unit_codecov
- uses: codecov/codecov-action@v1
Expand Down
2 changes: 1 addition & 1 deletion packages/console/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@flyteorg/console",
"version": "0.0.24",
"version": "0.0.25",
"description": "Flyteconsole main app module",
"main": "./dist/index.js",
"module": "./lib/index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/console/src/components/Errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DataError';
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as React from 'react';
import { useContext } from 'react';
import { useContext, useEffect } from 'react';
import { Collapse, IconButton } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import ExpandMore from '@material-ui/icons/ExpandMore';
import classnames from 'classnames';
import { LargeLoadingSpinner } from 'components/common/LoadingSpinner';
import {
LargeLoadingComponent,
LargeLoadingSpinner,
} from 'components/common/LoadingSpinner';
import { WaitForQuery } from 'components/common/WaitForQuery';
import { withRouteParams } from 'components/common/withRouteParams';
import { DataError } from 'components/Errors/DataError';
Expand All @@ -15,10 +18,13 @@ import { Workflow } from 'models/Workflow/types';
import { makeWorkflowQuery } from 'components/Workflow/workflowQueries';
import { ExecutionContext } from '../contexts';
import { useWorkflowExecutionQuery } from '../useWorkflowExecution';
import { ExecutionDetailsAppBarContent } from './ExecutionDetailsAppBarContent';
import { ExecutionMetadata } from './ExecutionMetadata';
import { ExecutionNodeViews } from './ExecutionNodeViews';
import { NodeExecutionDetailsContextProvider } from '../contextProvider/NodeExecutionDetails';
import {
NodeExecutionDetailsContextProvider,
WorkflowNodeExecutionsProvider,
} from '../contextProvider/NodeExecutionDetails';
import { useExecutionNodeViewsStatePoll } from './useExecutionNodeViewsState';

const useStyles = makeStyles((theme: Theme) => ({
expandCollapseButton: {
Expand All @@ -43,11 +49,35 @@ const useStyles = makeStyles((theme: Theme) => ({
},
}));

const RenderExecutionContainer: React.FC<{}> = () => {
const ExecutionContainer: React.FC<{}> = () => {
const styles = useStyles();
const [metadataExpanded, setMetadataExpanded] = React.useState(true);
const toggleMetadata = () => setMetadataExpanded(!metadataExpanded);

return (
<>
<div className={styles.metadataContainer}>
<Collapse in={metadataExpanded}>
<ExecutionMetadata />
</Collapse>
<div className={styles.expandCollapseContainer}>
<IconButton size="small" onClick={toggleMetadata}>
<ExpandMore
className={classnames(styles.expandCollapseButton, {
expanded: metadataExpanded,
})}
/>
</IconButton>
</div>
</div>
<ExecutionNodeViews />
</>
);
};

const RenderExecutionContainer: React.FC<React.PropsWithChildren<{}>> = ({
children,
}) => {
const { execution } = useContext(ExecutionContext);

const {
Expand All @@ -57,6 +87,9 @@ const RenderExecutionContainer: React.FC<{}> = () => {
const workflowQuery = useQuery<Workflow, Error>(
makeWorkflowQuery(useQueryClient(), workflowId),
);

// query to get all data to build Graph and Timeline
const { nodeExecutionsQuery } = useExecutionNodeViewsStatePoll(execution);
return (
<>
{/* Fetches the current workflow to build the execution tree inside NodeExecutionDetailsContextProvider */}
Expand All @@ -65,23 +98,17 @@ const RenderExecutionContainer: React.FC<{}> = () => {
<>
{/* Provides a node execution tree for the current workflow */}
<NodeExecutionDetailsContextProvider workflowId={workflow.id}>
<ExecutionDetailsAppBarContent />
<div className={styles.metadataContainer}>
<Collapse in={metadataExpanded}>
<ExecutionMetadata />
</Collapse>
<div className={styles.expandCollapseContainer}>
<IconButton size="small" onClick={toggleMetadata}>
<ExpandMore
className={classnames(styles.expandCollapseButton, {
expanded: metadataExpanded,
})}
/>
</IconButton>
</div>
</div>

<ExecutionNodeViews />
<WaitForQuery
query={nodeExecutionsQuery}
errorComponent={DataError}
loadingComponent={LargeLoadingComponent}
>
{data => (
<WorkflowNodeExecutionsProvider initialNodeExecutions={data}>
{children}
</WorkflowNodeExecutionsProvider>
)}
</WaitForQuery>
</NodeExecutionDetailsContextProvider>
</>
)}
Expand All @@ -90,17 +117,10 @@ const RenderExecutionContainer: React.FC<{}> = () => {
);
};

export interface ExecutionDetailsRouteParams {
domainId: string;
executionId: string;
projectId: string;
}
/** The view component for the Execution Details page */
export const ExecutionDetailsWrapper: React.FC<ExecutionDetailsRouteParams> = ({
executionId,
domainId,
projectId,
}) => {
export const ExecutionDetailsWrapper: React.FC<
React.PropsWithChildren<ExecutionDetailsRouteParams>
> = ({ children, executionId, domainId, projectId }) => {
const id = {
project: projectId,
domain: domainId,
Expand All @@ -122,13 +142,48 @@ export const ExecutionDetailsWrapper: React.FC<ExecutionDetailsRouteParams> = ({
execution,
}}
>
<RenderExecutionContainer />
<RenderExecutionContainer>{children}</RenderExecutionContainer>
</ExecutionContext.Provider>
)}
</WaitForQuery>
);
};

export function withExecutionDetails<P extends ExecutionDetailsRouteParams>(
WrappedComponent: React.FC<P>,
) {
return (props: P) => {
const [localRouteProps, setLocalRouteProps] = React.useState<P>();

useEffect(() => {
setLocalRouteProps(prev => {
if (JSON.stringify(prev) === JSON.stringify(props)) {
return prev;
}

return props;
});
}, [props]);

if (!localRouteProps) {
return <LargeLoadingComponent />;
}
return (
<ExecutionDetailsWrapper {...localRouteProps!}>
<WrappedComponent {...localRouteProps!} />
</ExecutionDetailsWrapper>
);
};
}

export interface ExecutionDetailsRouteParams {
domainId: string;
executionId: string;
projectId: string;
}

export const ExecutionDetails: React.FunctionComponent<
RouteComponentProps<ExecutionDetailsRouteParams>
> = withRouteParams<ExecutionDetailsRouteParams>(ExecutionDetailsWrapper);
> = withRouteParams<ExecutionDetailsRouteParams>(
withExecutionDetails(ExecutionContainer),
);
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
import React, { useContext } from 'react';
import { WaitForQuery } from 'components/common';
import { DataError } from 'components/Errors/DataError';
import { LargeLoadingComponent } from 'components/common/LoadingSpinner';
import { ExecutionContext } from '../contexts';
import { useExecutionNodeViewsStatePoll } from './useExecutionNodeViewsState';
import React from 'react';
import { ExecutionTabView } from './ExecutionTabView';
import { WorkflowNodeExecutionsProvider } from '../contextProvider/NodeExecutionDetails';
import { ExecutionDetailsAppBarContent } from './ExecutionDetailsAppBarContent';

/** Contains the available ways to visualize the nodes of a WorkflowExecution */
export const ExecutionNodeViews: React.FC<{}> = () => {
const { execution } = useContext(ExecutionContext);

// query to get all data to build Graph and Timeline
const { nodeExecutionsQuery } = useExecutionNodeViewsStatePoll(execution);

return (
<>
<WaitForQuery
query={nodeExecutionsQuery}
errorComponent={DataError}
loadingComponent={LargeLoadingComponent}
>
{data => (
<WorkflowNodeExecutionsProvider initialNodeExecutions={data}>
<ExecutionTabView />
</WorkflowNodeExecutionsProvider>
)}
</WaitForQuery>
<ExecutionDetailsAppBarContent />

<ExecutionTabView />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './ExecutionDetails';
export * from './ExecutionDetailsActions';
export * from './useExecutionNodeViewsState';
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import { nodeExecutionStatusFilters } from 'components/Executions/filters/status
import { oneFailedTaskWorkflow } from 'mocks/data/fixtures/oneFailedTaskWorkflow';
import { insertFixture } from 'mocks/data/insertFixture';
import { mockServer } from 'mocks/server';
import { Execution } from 'models/Execution/types';
import { Execution, NodeExecution } from 'models/Execution/types';
import { QueryClient, QueryClientProvider } from 'react-query';
import { createTestQueryClient } from 'test/utils';
import { ExecutionContext } from 'components/Executions/contexts';
import { listNodeExecutions, listTaskExecutions } from 'models/Execution/api';
import { NodeExecutionPhase } from 'models';
import { mockWorkflowId } from 'mocks/data/fixtures/types';
import { NodeExecutionDetailsContext } from 'components/Executions/contextProvider/NodeExecutionDetails';
import {
NodeExecutionDetailsContext,
WorkflowNodeExecutionsProvider,
} from 'components/Executions/contextProvider/NodeExecutionDetails';
import { transformerWorkflowToDag } from 'components/WorkflowGraph/transformerWorkflowToDag';
import { ExecutionNodeViews } from '../ExecutionNodeViews';
import { tabs } from '../constants';
Expand Down Expand Up @@ -54,27 +57,29 @@ jest.mock('components/WorkflowGraph/transformerWorkflowToDag', () => ({
transformerWorkflowToDag: jest.fn(),
}));

// ExecutionNodeViews uses query params for NE list, so we must match them
// for the list to be returned properly
const baseQueryParams = {
filters: '',
'sort_by.direction': 'ASCENDING',
'sort_by.key': 'created_at',
};

describe('ExecutionNodeViews', () => {
let queryClient: QueryClient;
let execution: Execution;
let fixture: ReturnType<typeof oneFailedTaskWorkflow.generate>;
let nodeExecutionsArray: NodeExecution[];
beforeEach(() => {
fixture = oneFailedTaskWorkflow.generate();
execution = fixture.workflowExecutions.top.data;
insertFixture(mockServer, fixture);
const nodeExecutions = fixture.workflowExecutions.top.nodeExecutions;
const nodeExecutionsArray = Object.values(nodeExecutions).map(
({ data }) => data,
);
nodeExecutionsArray = Object.values(nodeExecutions).map(({ data }) => data);

transformerWorkflowToDag.mockImplementation(_ => {
const nodes = nodeExecutionsArray.map(n => ({
id: n.id.nodeId,
scopedId: n.scopedId,
execution: n,
// type: dTypes.gateNode,
name: n.id.nodeId,
type: 3,
nodes: [],
edges: [],
}));
return {
dag: {
id: 'start-node',
Expand Down Expand Up @@ -113,16 +118,7 @@ describe('ExecutionNodeViews', () => {
nodes: [],
edges: [],
},
...nodeExecutionsArray.map(n => ({
id: n.id.nodeId,
scopedId: n.scopedId,
execution: n,
// type: dTypes.gateNode,
name: n.id.nodeId,
type: 3,
nodes: [],
edges: [],
})),
...nodes,
],
},
staticExecutionIdsMap: {},
Expand Down Expand Up @@ -161,7 +157,11 @@ describe('ExecutionNodeViews', () => {
workflowId: mockWorkflowId,
}}
>
<ExecutionNodeViews />
<WorkflowNodeExecutionsProvider
initialNodeExecutions={nodeExecutionsArray as any}
>
<ExecutionNodeViews />
</WorkflowNodeExecutionsProvider>
</NodeExecutionDetailsContext.Provider>
</ExecutionContext.Provider>
</QueryClientProvider>,
Expand Down Expand Up @@ -199,6 +199,7 @@ describe('ExecutionNodeViews', () => {

// Wait for succeeded task to disappear and ensure failed task remains
await fireEvent.click(failedFilter);

await waitFor(() => {
const nodes = queryAllByTestId('node-execution-row');
return nodes?.length === 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useMemo, useEffect, useState, useContext } from 'react';
import classnames from 'classnames';
import { useCommonStyles } from 'components/common/styles';
import scrollbarSize from 'dom-helpers/scrollbarSize';
import { NodeExecution, NodeExecutionsById } from 'models/Execution/types';
import { NodeExecution } from 'models/Execution/types';
import { merge, isEqual, cloneDeep } from 'lodash';
import { extractCompiledNodes } from 'components/hooks/utils';
import {
Expand All @@ -26,7 +26,11 @@ import { searchNode } from '../utils';
import { nodeExecutionPhaseConstants } from '../constants';
import { NodeExecutionDynamicProvider } from '../contextProvider/NodeExecutionDetails/NodeExecutionDynamicProvider';
import { ExecutionFilters } from '../ExecutionFilters';
import { ExecutionContext, FilteredNodeExecutions } from '../contexts';
import {
ExecutionContext,
FilteredNodeExecutions,
NodeExecutionsById,
} from '../contexts';
import { useExecutionNodeViewsStatePoll } from '../ExecutionDetails/useExecutionNodeViewsState';
import { stringifyIsEqual } from '../contextProvider/NodeExecutionDetails/utils';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './TaskExecutionDetails';
export * from './utils';
Loading

0 comments on commit 8f8d686

Please sign in to comment.