Skip to content

Commit

Permalink
[dagit] Separate snapshot and current pipeline permalinks
Browse files Browse the repository at this point in the history
Summary:
Separate a specific snapshot view from the main view of a pipeline. The snapshot permalink will have two tabs:

- Definition, for the state of the snapshot
- Runs, for runs specific to that snapshot ID

It will also display "Historical snapshot" or "Current snapshot" tags, depending on whether the viewed snapshot ID is the current one for the pipeline.

Breadcrumbing allows navigation back to the main pipeline page, where one can use the Playground and view all runs for the pipeline.

Stacked on top of the change that modifies how the Runs table rows are rendered. Screenshots below.

Test Plan:
View all existing `pipeline@snapshot` paths, verify that they display the correct breadcrumbs, tag, and tabs.

View `pipeline`-only paths, verify same.

Reviewers: bengotow, alangenfeld, sashank, catherinewu, prha

Reviewed By: bengotow

Subscribers: alangenfeld

Differential Revision: https://dagster.phacility.com/D4709
  • Loading branch information
hellendag committed Oct 13, 2020
1 parent 760f7ba commit f7dd62e
Show file tree
Hide file tree
Showing 16 changed files with 348 additions and 140 deletions.
43 changes: 4 additions & 39 deletions js_modules/dagit/src/App.tsx
Expand Up @@ -13,23 +13,17 @@ import {
import {APP_PATH_PREFIX} from 'src/DomUtils';
import {FeatureFlagsRoot} from 'src/FeatureFlagsRoot';
import {InstanceDetailsRoot} from 'src/InstanceDetailsRoot';
import {PipelineExplorerRoot} from 'src/PipelineExplorerRoot';
import {PipelineRunsRoot} from 'src/PipelineRunsRoot';
import {PythonErrorInfo} from 'src/PythonErrorInfo';
import {TimezoneProvider} from 'src/TimeComponents';
import {AssetsRoot} from 'src/assets/AssetsRoot';
import {PipelineExecutionRoot} from 'src/execute/PipelineExecutionRoot';
import {PipelineExecutionSetupRoot} from 'src/execute/PipelineExecutionSetupRoot';
import {useDocumentTitle} from 'src/hooks/useDocumentTitle';
import {LeftNav} from 'src/nav/LeftNav';
import {PipelineNav} from 'src/nav/PipelineNav';
import {PipelinePartitionsRoot} from 'src/partitions/PipelinePartitionsRoot';
import {PipelineOverviewRoot} from 'src/pipelines/PipelineOverviewRoot';
import {RunRoot} from 'src/runs/RunRoot';
import {PipelineRoot} from 'src/pipelines/PipelineRoot';
import {RunsRoot} from 'src/runs/RunsRoot';
import {ScheduleRoot} from 'src/schedules/ScheduleRoot';
import {SchedulerRoot} from 'src/schedules/SchedulerRoot';
import {SchedulesRoot} from 'src/schedules/SchedulesRoot';
import {SnapshotRoot} from 'src/snapshots/SnapshotRoot';
import {SolidDetailsRoot} from 'src/solids/SolidDetailsRoot';
import {SolidsRoot} from 'src/solids/SolidsRoot';

Expand All @@ -45,37 +39,8 @@ const AppRoutes = () => (
<Route path="/assets" component={AssetsRoot} exact={true} />
<Route path="/assets/(/?.*)" component={AssetsRoot} />
<Route path="/instance" component={InstanceDetailsRoot} />

<Route
path="/pipeline"
render={() => (
<div
style={{
display: 'flex',
flexDirection: 'column',
minWidth: 0,
width: '100%',
height: '100%',
}}
>
<PipelineNav />
<Switch>
<Route path="/pipeline/:pipelinePath/overview" component={PipelineOverviewRoot} />
<Route
path="/pipeline/:pipelinePath/playground/setup"
component={PipelineExecutionSetupRoot}
/>
<Route path="/pipeline/:pipelinePath/playground" component={PipelineExecutionRoot} />
<Route path="/pipeline/:pipelinePath/runs/:runId" component={RunRoot} />

<Route path="/pipeline/:pipelinePath/runs" component={PipelineRunsRoot} />
<Route path="/pipeline/:pipelinePath/partitions" component={PipelinePartitionsRoot} />
{/* Capture solid subpath in a regex match */}
<Route path="/pipeline/(/?.*)" component={PipelineExplorerRoot} />
</Switch>
</div>
)}
/>
<Route path="/pipeline/(.*)" component={PipelineRoot} />
<Route path="/snapshot/:pipelinePath/(.*)" component={SnapshotRoot} />

<DagsterRepositoryContext.Consumer>
{(context) =>
Expand Down
52 changes: 17 additions & 35 deletions js_modules/dagit/src/PipelineExplorerRoot.tsx
@@ -1,12 +1,11 @@
import {Colors, IconName, NonIdealState} from '@blueprintjs/core';
import {IconName, NonIdealState} from '@blueprintjs/core';
import {IconNames} from '@blueprintjs/icons';
import gql from 'graphql-tag';
import * as React from 'react';
import {useQuery} from 'react-apollo';
import {Redirect, RouteComponentProps} from 'react-router-dom';
import styled from 'styled-components/macro';

import {usePipelineSelector, useActivePipelineForName} from 'src/DagsterRepositoryContext';
import {usePipelineSelector} from 'src/DagsterRepositoryContext';
import {Loading} from 'src/Loading';
import {PipelineExplorer, PipelineExplorerOptions} from 'src/PipelineExplorer';
import {
Expand Down Expand Up @@ -133,8 +132,6 @@ export const PipelineExplorerRoot: React.FunctionComponent<RouteComponentProps>

const selectedName = explorerPath.pathSolids[explorerPath.pathSolids.length - 1];

const pipeline = useActivePipelineForName(explorerPath.pipelineName);

return (
<ExplorerSnapshotResolver explorerPath={explorerPath} options={options}>
{(result) => {
Expand Down Expand Up @@ -162,29 +159,22 @@ export const PipelineExplorerRoot: React.FunctionComponent<RouteComponentProps>
return <Redirect to={`/pipeline/${explorerPathToString(n)}`} />;
}

const pathID = explorerPath.snapshotId;

return (
<>
{pathID && pipeline?.pipelineSnapshotId !== pathID && (
<SnapshotNotice>You are viewing a historical pipeline snapshot.</SnapshotNotice>
)}
<PipelineExplorer
options={options}
setOptions={setOptions}
explorerPath={explorerPath}
history={props.history}
pipeline={result}
handles={displayedHandles}
parentHandle={parentHandle ? parentHandle : undefined}
selectedHandle={selectedHandle}
getInvocations={(definitionName) =>
displayedHandles
.filter((s) => s.solid.definition.name === definitionName)
.map((s) => ({handleID: s.handleID}))
}
/>
</>
<PipelineExplorer
options={options}
setOptions={setOptions}
explorerPath={explorerPath}
history={props.history}
pipeline={result}
handles={displayedHandles}
parentHandle={parentHandle ? parentHandle : undefined}
selectedHandle={selectedHandle}
getInvocations={(definitionName) =>
displayedHandles
.filter((s) => s.solid.definition.name === definitionName)
.map((s) => ({handleID: s.handleID}))
}
/>
);
}}
</ExplorerSnapshotResolver>
Expand Down Expand Up @@ -293,11 +283,3 @@ const ExplorerSnapshotResolver: React.FunctionComponent<ResolverProps> = ({
</Loading>
);
};

const SnapshotNotice = styled.div`
background: linear-gradient(to bottom, ${Colors.GOLD5}, ${Colors.GOLD4});
border-bottom: 1px solid ${Colors.GOLD3};
text-align: center;
padding: 4px 10px;
user-select: none;
`;
16 changes: 10 additions & 6 deletions js_modules/dagit/src/PipelineRunsRoot.tsx
Expand Up @@ -8,6 +8,7 @@ import styled from 'styled-components/macro';
import {CursorPaginationControls} from 'src/CursorPaginationControls';
import {ScrollContainer} from 'src/ListComponents';
import {Loading} from 'src/Loading';
import {explorerPathFromString} from 'src/PipelinePathUtils';
import {useDocumentTitle} from 'src/hooks/useDocumentTitle';
import {RunTable} from 'src/runs/RunTable';
import {RunsQueryRefetchContext} from 'src/runs/RunUtils';
Expand All @@ -24,14 +25,13 @@ import {
} from 'src/types/PipelineRunsRootQuery';

const PAGE_SIZE = 25;
const ENABLED_FILTERS: RunFilterTokenType[] = ['id', 'status', 'tag'];
const ENABLED_FILTERS: RunFilterTokenType[] = ['id', 'snapshotId', 'status', 'tag'];

export const PipelineRunsRoot: React.FunctionComponent<RouteComponentProps<{
pipelinePath: string;
}>> = ({match}) => {
const pipelineName = match.params.pipelinePath.split(':')[0];
const {pipelineName, snapshotId} = explorerPathFromString(match.params.pipelinePath);
useDocumentTitle(`Pipeline: ${pipelineName}`);

const [filterTokens, setFilterTokens] = useRunFiltering(ENABLED_FILTERS);

const {queryResult, paginationProps} = useCursorPaginatedQuery<
Expand All @@ -41,7 +41,7 @@ export const PipelineRunsRoot: React.FunctionComponent<RouteComponentProps<{
query: PIPELINE_RUNS_ROOT_QUERY,
pageSize: PAGE_SIZE,
variables: {
filter: {...runsFilterForSearchTokens(filterTokens), pipelineName},
filter: {...runsFilterForSearchTokens(filterTokens), pipelineName, snapshotId},
},
nextCursorForResult: (runs) => {
if (runs.pipelineRunsOrError.__typename !== 'PipelineRuns') {
Expand All @@ -57,6 +57,11 @@ export const PipelineRunsRoot: React.FunctionComponent<RouteComponentProps<{
},
});

const tokens = [{token: 'pipeline', value: pipelineName}, ...filterTokens];
if (snapshotId) {
tokens.push({token: 'snapshotId', value: snapshotId});
}

return (
<RunsQueryRefetchContext.Provider value={{refetch: queryResult.refetch}}>
<ScrollContainer>
Expand All @@ -71,7 +76,7 @@ export const PipelineRunsRoot: React.FunctionComponent<RouteComponentProps<{
<Filters>
<RunsFilter
enabledFilters={ENABLED_FILTERS}
tokens={[{token: 'pipeline', value: pipelineName}, ...filterTokens]}
tokens={tokens}
onChange={setFilterTokens}
loading={queryResult.loading}
/>
Expand Down Expand Up @@ -106,7 +111,6 @@ export const PipelineRunsRoot: React.FunctionComponent<RouteComponentProps<{
};

const Filters = styled.div`
float: right;
display: flex;
align-items: center;
margin-bottom: 14px;
Expand Down
2 changes: 1 addition & 1 deletion js_modules/dagit/src/TokenizingField.tsx
Expand Up @@ -355,7 +355,7 @@ export const TokenizingField: React.FunctionComponent<TokenizingFieldProps> = ({

const StyledTagInput = styled(TagInput)<{small?: boolean}>`
min-width: 400px;
max-width: 400px;
max-width: 600px;
input {
font-size: 12px;
}
Expand Down
24 changes: 12 additions & 12 deletions js_modules/dagit/src/__tests__/__snapshots__/App.test.tsx.snap
Expand Up @@ -529,7 +529,7 @@ exports[`renders execution 1`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -1327,7 +1327,7 @@ exports[`renders pipeline page 1`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -5226,7 +5226,7 @@ load_q2_sfo_weather:data_frame
</svg>
</div>
<div
className="sc-bdVaJa gXekTZ"
className="sc-EHOje lmfiVM"
id="zoom-slider-container"
>
<span
Expand Down Expand Up @@ -6084,7 +6084,7 @@ exports[`renders pipeline solid page 1`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -9983,7 +9983,7 @@ load_q2_sfo_weather:data_frame
</svg>
</div>
<div
className="sc-bdVaJa gXekTZ"
className="sc-EHOje lmfiVM"
id="zoom-slider-container"
>
<span
Expand Down Expand Up @@ -10862,7 +10862,7 @@ exports[`renders solids explorer 1`] = `
onClick={[Function]}
>
<div
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eDKGJi"
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eoYAEk"
onBlur={[Function]}
onClick={[Function]}
>
Expand Down Expand Up @@ -12704,7 +12704,7 @@ exports[`renders type page 1`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -16603,7 +16603,7 @@ load_q2_sfo_weather:data_frame
</svg>
</div>
<div
className="sc-bdVaJa gXekTZ"
className="sc-EHOje lmfiVM"
id="zoom-slider-container"
>
<span
Expand Down Expand Up @@ -17461,7 +17461,7 @@ exports[`renders type page 2`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -21360,7 +21360,7 @@ load_q2_sfo_weather:data_frame
</svg>
</div>
<div
className="sc-bdVaJa gXekTZ"
className="sc-EHOje lmfiVM"
id="zoom-slider-container"
>
<span
Expand Down Expand Up @@ -22218,7 +22218,7 @@ exports[`renders without error 1`] = `
}
>
<div
className="TopNav__PipelineTabBarContainer-uwq7ub-3 igQCby"
className="TopNav__PipelineTabBarContainer-uwq7ub-3 fbpxdY"
>
<div
className="TopNav__BreadcrumbContainer-uwq7ub-2 bQinae"
Expand Down Expand Up @@ -26117,7 +26117,7 @@ load_q2_sfo_weather:data_frame
</svg>
</div>
<div
className="sc-bdVaJa gXekTZ"
className="sc-EHOje lmfiVM"
id="zoom-slider-container"
>
<span
Expand Down
Expand Up @@ -9,7 +9,7 @@ exports[`renders empty [snapshot] 1`] = `
onClick={[Function]}
>
<div
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eDKGJi"
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eoYAEk"
onBlur={[Function]}
onClick={[Function]}
>
Expand Down Expand Up @@ -42,7 +42,7 @@ exports[`renders with tokens [snapshot] 1`] = `
onClick={[Function]}
>
<div
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eDKGJi"
className="bp3-input bp3-tag-input TokenizingField__StyledTagInput-ebq0ud-0 eoYAEk"
onBlur={[Function]}
onClick={[Function]}
>
Expand Down
9 changes: 7 additions & 2 deletions js_modules/dagit/src/execute/PipelineExecutionRoot.tsx
Expand Up @@ -3,7 +3,7 @@ import {IconNames} from '@blueprintjs/icons';
import gql from 'graphql-tag';
import * as React from 'react';
import {Query} from 'react-apollo';
import {RouteComponentProps} from 'react-router-dom';
import {Redirect, RouteComponentProps} from 'react-router-dom';

import {
usePipelineSelector,
Expand All @@ -16,6 +16,7 @@ import {
applyCreateSession,
useStorage,
} from 'src/LocalStorage';
import {explorerPathFromString} from 'src/PipelinePathUtils';
import {
ExecutionSessionContainer,
ExecutionSessionContainerError,
Expand All @@ -35,7 +36,7 @@ import {useDocumentTitle} from 'src/hooks/useDocumentTitle';
export const PipelineExecutionRoot: React.FunctionComponent<RouteComponentProps<{
pipelinePath: string;
}>> = ({match}) => {
const pipelineName = match.params.pipelinePath.split(':')[0];
const {pipelineName, snapshotId} = explorerPathFromString(match.params.pipelinePath);
useDocumentTitle(`Pipeline: ${pipelineName}`);

const {loading} = useRepositoryOptions();
Expand All @@ -45,6 +46,10 @@ export const PipelineExecutionRoot: React.FunctionComponent<RouteComponentProps<
const session = data.sessions[data.current];
const pipelineSelector = usePipelineSelector(pipelineName, session?.solidSelection || undefined);

if (snapshotId) {
return <Redirect to={`/pipeline/${pipelineName}/playground`} />;
}

const onSaveSession = (session: string, changes: IExecutionSessionChanges) => {
onSave(applyChangesToSession(data, session, changes));
};
Expand Down

0 comments on commit f7dd62e

Please sign in to comment.