Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: execution view children fetch on demand refactor #676

Merged
merged 14 commits into from
Feb 14, 2023
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import * as React from 'react';
import React, { useEffect } from 'react';
import { Tab, Tabs } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { WaitForQuery } from 'components/common/WaitForQuery';
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 { useEffect, useMemo, useState } from 'react';
import { Execution } from 'models/Execution/types';
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';
Expand All @@ -23,10 +15,8 @@ import { ExecutionFilters } from '../ExecutionFilters';
import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState';
import { tabs } from './constants';
import { useExecutionNodeViewsState } from './useExecutionNodeViewsState';
import { fetchTaskExecutionList } from '../taskExecutionQueries';
import { getGroupedLogs } from '../TaskExecutionsList/utils';
import { useAllTreeNodeExecutionGroupsQuery } from '../nodeExecutionQueries';
import { ExecutionTab } from './ExecutionTab';
import { useNodeExecutionsById } from '../useNodeExecutionsById';

const useStyles = makeStyles((theme: Theme) => ({
filters: {
Expand Down Expand Up @@ -55,10 +45,6 @@ const isPhaseFilter = (appliedFilters: FilterOperation[]) => {
return false;
};

interface WorkflowNodeExecution extends NodeExecution {
logsByPhase?: LogsByPhase;
}

interface ExecutionNodeViewsProps {
execution: Execution;
}
Expand All @@ -71,96 +57,28 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
const styles = useStyles();
const filterState = useNodeExecutionFiltersState();
const tabState = useTabState(tabs, defaultTab);
const queryClient = useQueryClient();
const [nodeExecutionsLoading, setNodeExecutionsLoading] =
useState<boolean>(true);

const {
closure: { workflowId },
} = execution;

const [nodeExecutions, setNodeExecutions] = useState<NodeExecution[]>([]);
const [nodeExecutionsWithResources, setNodeExecutionsWithResources] =
useState<WorkflowNodeExecution[]>([]);

const nodeExecutionsById = useMemo(() => {
return keyBy(nodeExecutionsWithResources, 'scopedId');
}, [nodeExecutionsWithResources]);

// 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;

async function fetchData(baseNodeExecutions, queryClient) {
setNodeExecutionsLoading(true);
const newValue = await Promise.all(
baseNodeExecutions.map(async baseNodeExecution => {
const taskExecutions = await fetchTaskExecutionList(
queryClient,
baseNodeExecution.id,
);

const useNewMapTaskView = taskExecutions.every(taskExecution => {
const {
closure: { taskType, metadata, eventVersion = 0 },
} = taskExecution;
return isMapTaskV1(
eventVersion,
metadata?.externalResources?.length ?? 0,
taskType ?? undefined,
);
});
const externalResources: ExternalResource[] = taskExecutions
.map(
taskExecution =>
taskExecution.closure.metadata?.externalResources,
)
.flat()
.filter((resource): resource is ExternalResource => !!resource);

const logsByPhase: LogsByPhase = getGroupedLogs(externalResources);

return {
...baseNodeExecution,
...(useNewMapTaskView && logsByPhase.size > 0 && { logsByPhase }),
};
}),
);

if (isCurrent) {
setNodeExecutionsWithResources(newValue);
setNodeExecutionsLoading(false);
}
}

if (nodeExecutions.length > 0) {
fetchData(nodeExecutions, queryClient);
} else {
if (isCurrent) {
setNodeExecutionsLoading(false);
}
}
return () => {
isCurrent = false;
};
}, [nodeExecutions]);

const childGroupsQuery = useAllTreeNodeExecutionGroupsQuery(
nodeExecutionsQuery.data ?? [],
{},
);
const { nodeExecutionsById, setCurrentNodeExecutionsById } =
useNodeExecutionsById();

useEffect(() => {
if (!childGroupsQuery.isLoading && childGroupsQuery.data) {
setNodeExecutions(childGroupsQuery.data);
}
}, [childGroupsQuery.data]);
const currentNodeExecutionsById = keyBy(
nodeExecutionsQuery.data,
'scopedId',
);
setCurrentNodeExecutionsById(currentNodeExecutionsById);
}, [nodeExecutionsQuery.data]);

const LoadingComponent = () => {
return (
Expand All @@ -171,27 +89,16 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
};

const renderTab = tabType => {
if (nodeExecutionsLoading) {
return <LoadingComponent />;
}
return (
<WaitForQuery
errorComponent={DataError}
query={childGroupsQuery}
loadingComponent={LoadingComponent}
>
{() => (
<ExecutionTab
tabType={tabType}
// if only phase filter was applied, ignore request response, and filter out nodes via frontend filter
filteredNodeExecutions={
isPhaseFilter(filterState.appliedFilters)
? undefined
: filteredNodeExecutions
}
/>
)}
</WaitForQuery>
<ExecutionTab
tabType={tabType}
// if only phase filter was applied, ignore request response, and filter out nodes via frontend filter
filteredNodeExecutions={
isPhaseFilter(filterState.appliedFilters)
? undefined
: filteredNodeExecutions
}
/>
);
};

Expand All @@ -203,7 +110,9 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({
<Tab value={tabs.timeline.id} label={tabs.timeline.label} />
</Tabs>
<NodeExecutionDetailsContextProvider workflowId={workflowId}>
<NodeExecutionsByIdContext.Provider value={nodeExecutionsById}>
<NodeExecutionsByIdContext.Provider
value={{ nodeExecutionsById, setCurrentNodeExecutionsById }}
>
<div className={styles.nodesContainer}>
{tabState.value === tabs.nodes.id && (
<div className={styles.filters}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { WorkflowGraph } from 'components/WorkflowGraph/WorkflowGraph';
import { TaskExecutionPhase } from 'models/Execution/enums';
import { NodeExecution, NodeExecutionIdentifier } from 'models/Execution/types';
import { startNodeId, endNodeId } from 'models/Node/constants';
import * as React from 'react';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { transformerWorkflowToDag } from 'components/WorkflowGraph/transformerWorkflowToDag';
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,
Expand Down Expand Up @@ -70,31 +69,50 @@ export const ExecutionTabContent: React.FC<ExecutionTabContentProps> = ({
const styles = useStyles();
const { compiledWorkflowClosure } = useNodeExecutionContext();
const { appliedFilters } = useNodeExecutionFiltersState();
const nodeExecutionsById = useContext(NodeExecutionsByIdContext);

const { dag, staticExecutionIdsMap, error } = compiledWorkflowClosure
const { nodeExecutionsById } = useContext(NodeExecutionsByIdContext);
const { staticExecutionIdsMap } = compiledWorkflowClosure
? transformerWorkflowToDag(compiledWorkflowClosure)
: { dag: {}, staticExecutionIdsMap: {}, error: null };
const dynamicParents = checkForDynamicExecutions(
nodeExecutionsById,
staticExecutionIdsMap,
: { staticExecutionIdsMap: {} };
const [dynamicParents, setDynamicParents] = useState(
checkForDynamicExecutions(nodeExecutionsById, staticExecutionIdsMap),
);
const { data: dynamicWorkflows } = useQuery(
const { data: dynamicWorkflows, refetch } = useQuery(
makeNodeExecutionDynamicWorkflowQuery(dynamicParents),
);

const [initialNodes, setInitialNodes] = useState<dNode[]>([]);
const [initialFilteredNodes, setInitialFilteredNodes] = useState<
dNode[] | undefined
>(undefined);
const [dagError, setDagError] = useState(null);
const [mergedDag, setMergedDag] = useState(null);
const [filters, setFilters] = useState<FilterOperation[]>(appliedFilters);
const [isFiltersChanged, setIsFiltersChanged] = useState<boolean>(false);
const [shouldUpdate, setShouldUpdate] = useState<boolean>(false);

useEffect(() => {
if (shouldUpdate) {
const newDynamicParents = checkForDynamicExecutions(
nodeExecutionsById,
staticExecutionIdsMap,
);
setDynamicParents(newDynamicParents);
refetch();
setShouldUpdate(false);
}
}, [shouldUpdate]);

useEffect(() => {
const nodes: dNode[] = compiledWorkflowClosure
? transformerWorkflowToDag(compiledWorkflowClosure, dynamicWorkflows).dag
.nodes
: [];
const { dag, staticExecutionIdsMap, error } = compiledWorkflowClosure
? transformerWorkflowToDag(
compiledWorkflowClosure,
dynamicWorkflows,
nodeExecutionsById,
)
: { dag: {}, staticExecutionIdsMap: {}, error: null };

const nodes = dag.nodes ?? [];

// we remove start/end node info in the root dNode list during first assignment
const plainNodes = convertToPlainNodes(nodes);

Expand All @@ -106,14 +124,27 @@ export const ExecutionTabContent: React.FC<ExecutionTabContentProps> = ({
const dynamicWorkflow = transformerWorkflowToDag(
compiledWorkflowClosure,
dynamicWorkflows,
nodeExecutionsById,
);
newMergedDag = dynamicWorkflow.dag;
}
}
}
setDagError(error);
setMergedDag(newMergedDag);
plainNodes.map(node => {
const initialNode = initialNodes.find(n => n.scopedId === node.scopedId);
if (initialNode) {
node.expanded = initialNode.expanded;
}
});
setInitialNodes(plainNodes);
}, [compiledWorkflowClosure, dynamicWorkflows]);
}, [
compiledWorkflowClosure,
dynamicWorkflows,
dynamicParents,
nodeExecutionsById,
]);

useEffect(() => {
if (!isEqual(filters, appliedFilters)) {
Expand Down Expand Up @@ -219,19 +250,22 @@ export const ExecutionTabContent: React.FC<ExecutionTabContentProps> = ({
<NodeExecutionsTable
initialNodes={initialNodes}
filteredNodes={initialFilteredNodes}
setShouldUpdate={setShouldUpdate}
/>
);
case tabs.graph.id:
return (
<WorkflowGraph
mergedDag={mergedDag}
error={error}
error={dagError}
dynamicWorkflows={dynamicWorkflows}
initialNodes={initialNodes}
onNodeSelectionChanged={onNodeSelectionChanged}
selectedPhase={selectedPhase}
onPhaseSelectionChanged={setSelectedPhase}
isDetailsTabClosed={isDetailsTabClosed}
shouldUpdate={shouldUpdate}
setShouldUpdate={setShouldUpdate}
/>
);
case tabs.timeline.id:
Expand All @@ -241,6 +275,7 @@ export const ExecutionTabContent: React.FC<ExecutionTabContentProps> = ({
<ExecutionTimeline
chartTimezone={chartTimezone}
initialNodes={initialNodes}
setShouldUpdate={setShouldUpdate}
/>
</div>
<ExecutionTimelineFooter onTimezoneChange={handleTimezoneChange} />
Expand Down
Loading