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: integrate timeline and graph tabs wrappers under one component #572

Merged
merged 7 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersStat
import { NodeExecutionsTable } from '../Tables/NodeExecutionsTable';
import { tabs } from './constants';
import { useExecutionNodeViewsState } from './useExecutionNodeViewsState';
import { ExecutionNodesTimeline } from './Timeline';
import { fetchTaskExecutionList } from '../taskExecutionQueries';
import { getGroupedLogs } from '../TaskExecutionsList/utils';
import { useAllTreeNodeExecutionGroupsQuery } from '../nodeExecutionQueries';
import { ExecutionWorkflowGraph } from './ExecutionWorkflowGraph';
import { ExecutionTab } from './ExecutionTab';

const useStyles = makeStyles((theme: Theme) => ({
filters: {
Expand Down Expand Up @@ -148,24 +147,13 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({ executio
</NodeExecutionsRequestConfigContext.Provider>
);

const renderExecutionChildrenLoader = () =>
nodeExecutions.length > 0 ? <ExecutionWorkflowGraph workflowId={workflowId} /> : null;

const renderExecutionLoader = () => {
return (
<WaitForQuery errorComponent={DataError} query={childGroupsQuery}>
{renderExecutionChildrenLoader}
</WaitForQuery>
);
};

const renderExecutionsTimeline = () => (
const renderTab = (tabType) => (
<WaitForQuery
errorComponent={DataError}
query={childGroupsQuery}
loadingComponent={TimelineLoading}
>
{() => <ExecutionNodesTimeline />}
{() => <ExecutionTab tabType={tabType} />}
</WaitForQuery>
);

Expand All @@ -186,28 +174,24 @@ export const ExecutionNodeViews: React.FC<ExecutionNodeViewsProps> = ({ executio
</Tabs>
<NodeExecutionDetailsContextProvider workflowId={workflowId}>
<NodeExecutionsByIdContext.Provider value={nodeExecutionsById}>
<div className={styles.nodesContainer}>
{tabState.value === tabs.nodes.id && (
<>
<div className={styles.filters}>
<ExecutionFilters {...filterState} />
</div>
{nodeExecutions.length > 0 ? (
<div className={styles.nodesContainer}>
{tabState.value === tabs.nodes.id ? (
<>
<div className={styles.filters}>
<ExecutionFilters {...filterState} />
</div>
<WaitForQuery errorComponent={DataError} query={nodeExecutionsQuery}>
{renderNodeExecutionsTable}
</WaitForQuery>
</>
) : (
<WaitForQuery errorComponent={DataError} query={nodeExecutionsQuery}>
{renderNodeExecutionsTable}
{() => renderTab(tabState.value)}
</WaitForQuery>
</>
)}
{tabState.value === tabs.graph.id && (
<WaitForQuery errorComponent={DataError} query={nodeExecutionsQuery}>
{renderExecutionLoader}
</WaitForQuery>
)}
{tabState.value === tabs.timeline.id && (
<WaitForQuery errorComponent={DataError} query={nodeExecutionsQuery}>
{renderExecutionsTimeline}
</WaitForQuery>
)}
</div>
)}
</div>
) : null}
</NodeExecutionsByIdContext.Provider>
</NodeExecutionDetailsContextProvider>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { makeStyles } from '@material-ui/core';
import { DetailsPanel } from 'components/common/DetailsPanel';
import { WaitForQuery } from 'components/common/WaitForQuery';
import { DataError } from 'components/Errors/DataError';
import { makeWorkflowQuery } from 'components/Workflow/workflowQueries';
import { WorkflowGraph } from 'components/WorkflowGraph/WorkflowGraph';
import { TaskExecutionPhase } from 'models/Execution/enums';
import { NodeExecutionIdentifier } from 'models/Execution/types';
import { startNodeId, endNodeId } from 'models/Node/constants';
import { Workflow } from 'models/Workflow/types';
import * as React from 'react';
import { useContext, useEffect, useMemo, useState } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails';
import { NodeExecutionsByIdContext } from '../contexts';
import { tabs } from './constants';
import { NodeExecutionDetailsPanelContent } from './NodeExecutionDetailsPanelContent';
import { NodeExecutionsTimelineContext } from './Timeline/context';
import { ExecutionTimeline } from './Timeline/ExecutionTimeline';
import { ExecutionTimelineFooter } from './Timeline/ExecutionTimelineFooter';
import { TimeZone } from './Timeline/helpers';
import { ScaleProvider } from './Timeline/scaleContext';

export interface ExecutionTabProps {
tabType: string;
}

const useStyles = makeStyles(() => ({
wrapper: {
display: 'flex',
flexDirection: 'column',
flex: '1 1 100%',
},
container: {
display: 'flex',
flex: '1 1 0',
overflowY: 'auto',
},
}));

/** Contains the available ways to visualize the nodes of a WorkflowExecution */
export const ExecutionTab: React.FC<ExecutionTabProps> = ({ tabType }) => {
const styles = useStyles();
const queryClient = useQueryClient();
const { workflowId } = useNodeExecutionContext();
const workflowQuery = useQuery<Workflow, Error>(makeWorkflowQuery(queryClient, workflowId));

const [selectedNodes, setSelectedNodes] = useState<string[]>([]);
const nodeExecutionsById = useContext(NodeExecutionsByIdContext);

// Note: flytegraph allows multiple selection, but we only support showing
// a single item in the details panel
const [selectedExecution, setSelectedExecution] = useState<NodeExecutionIdentifier | null>(
selectedNodes.length
? nodeExecutionsById[selectedNodes[0]]
? nodeExecutionsById[selectedNodes[0]].id
: {
nodeId: selectedNodes[0],
executionId: nodeExecutionsById[Object.keys(nodeExecutionsById)[0]].id.executionId,
}
: null,
);

const [selectedPhase, setSelectedPhase] = useState<TaskExecutionPhase | undefined>(undefined);
const [isDetailsTabClosed, setIsDetailsTabClosed] = useState<boolean>(!selectedExecution);

useEffect(() => {
setIsDetailsTabClosed(!selectedExecution);
}, [selectedExecution]);

const onNodeSelectionChanged = (newSelection: string[]) => {
const validSelection = newSelection.filter((nodeId) => {
if (nodeId === startNodeId || nodeId === endNodeId) {
return false;
}
return true;
});
setSelectedNodes(validSelection);
const newSelectedExecution = validSelection.length
? nodeExecutionsById[validSelection[0]]
? nodeExecutionsById[validSelection[0]].id
: {
nodeId: validSelection[0],
executionId: nodeExecutionsById[Object.keys(nodeExecutionsById)[0]].id.executionId,
}
: null;
setSelectedExecution(newSelectedExecution);
};

const onCloseDetailsPanel = () => {
setSelectedExecution(null);
setSelectedPhase(undefined);
setSelectedNodes([]);
};

const [chartTimezone, setChartTimezone] = useState(TimeZone.Local);

const handleTimezoneChange = (tz) => setChartTimezone(tz);

const timelineContext = useMemo(
() => ({ selectedExecution, setSelectedExecution }),
[selectedExecution, setSelectedExecution],
);

const renderGraph = (workflow: Workflow) => (
<WorkflowGraph
onNodeSelectionChanged={onNodeSelectionChanged}
selectedPhase={selectedPhase}
onPhaseSelectionChanged={setSelectedPhase}
isDetailsTabClosed={isDetailsTabClosed}
workflow={workflow}
/>
);

return (
<ScaleProvider>
{tabType === tabs.timeline.id && (
<div className={styles.wrapper}>
<div className={styles.container}>
<NodeExecutionsTimelineContext.Provider value={timelineContext}>
<ExecutionTimeline chartTimezone={chartTimezone} />;
</NodeExecutionsTimelineContext.Provider>
</div>
<ExecutionTimelineFooter onTimezoneChange={handleTimezoneChange} />
</div>
)}
{tabType === tabs.graph.id && (
<WaitForQuery errorComponent={DataError} query={workflowQuery}>
{renderGraph}
</WaitForQuery>
)}
{/* Side panel, shows information for specific node */}
<DetailsPanel open={!isDetailsTabClosed} onClose={onCloseDetailsPanel}>
{!isDetailsTabClosed && selectedExecution && (
<NodeExecutionDetailsPanelContent
onClose={onCloseDetailsPanel}
phase={selectedPhase}
nodeExecutionId={selectedExecution}
/>
)}
</DetailsPanel>
</ScaleProvider>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,14 @@ export const ExecutionTimeline: React.FC<ExProps> = ({ chartTimezone }) => {
const execution = nodeExecutionsById[node.scopedId];
return {
...node,
startedAt: execution?.closure.startedAt,
execution,
};
});
setShowNodes(updatedShownNodesMap);

// set startTime for all timeline offset and duration calculations.
const firstStartedAt = updatedShownNodesMap[0]?.execution?.closure.startedAt;
const firstStartedAt = updatedShownNodesMap[0]?.startedAt;
if (firstStartedAt) {
setStartedAt(timestampToDate(firstStartedAt));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { SelectNodeExecutionLink } from 'components/Executions/Tables/SelectNode
import { isEqual } from 'lodash';
import { NodeExecution } from 'models/Execution/types';
import * as React from 'react';
import { NodeExecutionsTimelineContextData } from './context';
import { useContext } from 'react';
import { NodeExecutionsTimelineContext } from './context';

interface NodeExecutionTimelineNameData {
name: string;
execution: NodeExecution;
state: NodeExecutionsTimelineContextData;
}

const useStyles = makeStyles((_theme: Theme) => ({
Expand All @@ -19,22 +19,19 @@ const useStyles = makeStyles((_theme: Theme) => ({
},
}));

export const NodeExecutionName: React.FC<NodeExecutionTimelineNameData> = ({
name,
execution,
state,
}) => {
export const NodeExecutionName: React.FC<NodeExecutionTimelineNameData> = ({ name, execution }) => {
const commonStyles = useCommonStyles();
const styles = useStyles();

const { selectedExecution, setSelectedExecution } = useContext(NodeExecutionsTimelineContext);

if (!execution) {
// to avoid crash - disable items which do not have associated execution.
// as we won't be able to provide task info for them anyway.
return <Typography variant="body1">{name}</Typography>;
}

const isSelected =
state.selectedExecution != null && isEqual(execution.id, state.selectedExecution);
const isSelected = selectedExecution != null && isEqual(execution.id, selectedExecution);
return isSelected ? (
<Typography variant="body1" className={styles.selectedExecutionName}>
{name}
Expand All @@ -44,7 +41,7 @@ export const NodeExecutionName: React.FC<NodeExecutionTimelineNameData> = ({
className={commonStyles.primaryLink}
execution={execution}
linkText={name}
state={state}
setSelectedExecution={setSelectedExecution}
/>
);
};
Loading