Skip to content

Commit

Permalink
[ui] Show targeted asset checks in run list, run details header (#16519)
Browse files Browse the repository at this point in the history
## Summary & Motivation

This builds on the GraphQL in #16510. Runs that had an
assetCheckSelection now show those asset checks in both the run list and
run details page.

<img width="1114" alt="image"
src="https://github.com/dagster-io/dagster/assets/1037212/3633b304-d6fb-473d-abcb-5e01b0bfb4d9">
<img width="704" alt="image"
src="https://github.com/dagster-io/dagster/assets/1037212/eab03c6a-1ac6-4361-b357-96553c881e9a">


## How I Tested These Changes

We have surprisingly no storybook coverage of these two run components
-- I will add some stories in a follow-up once the urgent stuff for
tomorrow's release is done!

---------

Co-authored-by: bengotow <[email protected]>
  • Loading branch information
2 people authored and johannkm committed Sep 15, 2023
1 parent 3c99b8f commit 71ee0aa
Show file tree
Hide file tree
Showing 23 changed files with 215 additions and 38 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import styled from 'styled-components';
import {TimestampDisplay} from '../../schedules/TimestampDisplay';
import {testId} from '../../testing/testId';
import {HeaderCell, Row, RowCell, Container, Inner} from '../../ui/VirtualizedTable';
import {assetDetailsPathForKey} from '../assetDetailsPathForKey';
import {assetDetailsPathForAssetCheck} from '../assetDetailsPathForKey';

import {MetadataCell} from './AssetCheckDetailModal';
import {AssetCheckStatusTag} from './AssetCheckStatusTag';
Expand Down Expand Up @@ -80,10 +80,7 @@ export const VirtualizedAssetCheckRow = ({assetNode, height, start, row}: AssetC
<RowCell style={{flexDirection: 'row', alignItems: 'center'}}>
<Box flex={{direction: 'column', gap: 4}}>
<Link
to={assetDetailsPathForKey(assetNode.assetKey, {
view: 'checks',
checkDetail: row.name,
})}
to={assetDetailsPathForAssetCheck({assetKey: assetNode.assetKey, name: row.name})}
>
<Body2>{row.name}</Body2>
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ import {AssetKey, AssetViewParams} from './types';
export const assetDetailsPathForKey = (key: AssetKey, query?: AssetViewParams) => {
return `/assets/${key.path.map(encodeURIComponent).join('/')}?${qs.stringify(query)}`;
};

export const assetDetailsPathForAssetCheck = (check: {assetKey: AssetKey; name: string}) => {
return assetDetailsPathForKey(check.assetKey, {view: 'checks', checkDetail: check.name});
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {isHiddenAssetGroupJob} from '../../asset-graph/Utils';
import {BulkActionStatus, RunStatus} from '../../graphql/types';
import {PartitionStatus, PartitionStatusHealthSourceOps} from '../../partitions/PartitionStatus';
import {PipelineReference} from '../../pipelines/PipelineReference';
import {AssetKeyTagCollection} from '../../runs/AssetKeyTagCollection';
import {AssetKeyTagCollection} from '../../runs/AssetTagCollections';
import {inProgressStatuses} from '../../runs/RunStatuses';
import {RunStatusTagsWithCounts} from '../../runs/RunTimeline';
import {runsPathWithFilters} from '../../runs/RunsFilterInput';
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,44 @@ import * as React from 'react';
import {Link} from 'react-router-dom';

import {displayNameForAssetKey} from '../asset-graph/Utils';
import {assetDetailsPathForKey} from '../assets/assetDetailsPathForKey';
import {
assetDetailsPathForAssetCheck,
assetDetailsPathForKey,
} from '../assets/assetDetailsPathForKey';
import {globalAssetGraphPathForAssetsAndDescendants} from '../assets/globalAssetGraphPathToString';
import {AssetKey} from '../assets/types';
import {TagActionsPopover} from '../ui/TagActions';
import {VirtualizedItemListForDialog} from '../ui/VirtualizedItemListForDialog';

export const AssetKeyTagCollection: React.FC<{
assetKeys: AssetKey[] | null;
modalTitle?: string;
useTags?: boolean;
}> = React.memo(({assetKeys, useTags, modalTitle = 'Assets in Run'}) => {
const [showMore, setShowMore] = React.useState(false);
const renderItemAssetKey = (assetKey: AssetKey) => (
<Link to={assetDetailsPathForKey(assetKey)}>{displayNameForAssetKey(assetKey)}</Link>
);

if (!assetKeys || !assetKeys.length) {
return null;
}
const renderItemAssetCheck = (assetCheck: Check) => (
<Link to={assetDetailsPathForAssetCheck(assetCheck)}>{labelForAssetCheck(assetCheck)}</Link>
);

const labelForAssetCheck = (check: Check) => {
return `${check.name} on ${displayNameForAssetKey(check.assetKey)}`;
};

const showMoreDialog =
assetKeys.length > 1 ? (
function useShowMoreDialog<T>(
modalTitle: string,
items: T[] | null,
renderItem: (item: T) => React.ReactNode,
) {
const [showMore, setShowMore] = React.useState(false);

const dialog =
!!items && items.length > 1 ? (
<Dialog
title={modalTitle}
onClose={() => setShowMore(false)}
style={{minWidth: '400px', maxWidth: '80vw', maxHeight: '70vh'}}
isOpen={showMore}
>
<div style={{height: '340px', overflow: 'hidden'}}>
<VirtualizedItemListForDialog
items={assetKeys}
renderItem={(assetKey: AssetKey) => (
<Link to={assetDetailsPathForKey(assetKey)}>{displayNameForAssetKey(assetKey)}</Link>
)}
/>
<VirtualizedItemListForDialog items={items} renderItem={renderItem} />
</div>
<DialogFooter topBorder>
<Button autoFocus onClick={() => setShowMore(false)}>
Expand All @@ -53,6 +59,20 @@ export const AssetKeyTagCollection: React.FC<{
</Dialog>
) : undefined;

return {dialog, showMore, setShowMore};
}

export const AssetKeyTagCollection: React.FC<{
assetKeys: AssetKey[] | null;
modalTitle?: string;
useTags?: boolean;
}> = React.memo(({assetKeys, useTags, modalTitle = 'Assets in run'}) => {
const {setShowMore, dialog} = useShowMoreDialog(modalTitle, assetKeys, renderItemAssetKey);

if (!assetKeys || !assetKeys.length) {
return null;
}

if (assetKeys.length === 1) {
// Outer span ensures the popover target is in the right place if the
// parent is a flexbox.
Expand Down Expand Up @@ -122,7 +142,78 @@ export const AssetKeyTagCollection: React.FC<{
</ButtonLink>
)}
</TagActionsPopover>
{showMoreDialog}
{dialog}
</span>
);
});

type Check = {name: string; assetKey: AssetKey};

export const AssetCheckTagCollection: React.FC<{
assetChecks: Check[] | null;
modalTitle?: string;
useTags?: boolean;
}> = React.memo(({assetChecks, useTags, modalTitle = 'Asset checks in run'}) => {
const {setShowMore, dialog} = useShowMoreDialog(modalTitle, assetChecks, renderItemAssetCheck);

if (!assetChecks || !assetChecks.length) {
return null;
}

if (assetChecks.length === 1) {
// Outer span ensures the popover target is in the right place if the
// parent is a flexbox.
const check = assetChecks[0]!;
return (
<span style={useTags ? {} : {marginBottom: -4}}>
<TagActionsPopover
data={{key: '', value: ''}}
actions={[{label: 'View asset check', to: assetDetailsPathForAssetCheck(check)}]}
>
{useTags ? (
<Tag intent="none" interactive icon="asset_check">
{labelForAssetCheck(check)}
</Tag>
) : (
<Link to={assetDetailsPathForAssetCheck(check)}>
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
<Icon color={Colors.Gray400} name="asset_check" size={16} />
{labelForAssetCheck(check)}
</Box>
</Link>
)}
</TagActionsPopover>
</span>
);
}

return (
<span style={useTags ? {} : {marginBottom: -4}}>
<TagActionsPopover
data={{key: '', value: ''}}
actions={[
{
label: 'View list',
onClick: () => setShowMore(true),
},
]}
>
{useTags ? (
<Tag intent="none" icon="asset_check">
{assetChecks.length} asset checks
</Tag>
) : (
<ButtonLink onClick={() => setShowMore(true)}>
<Box flex={{direction: 'row', gap: 8, alignItems: 'center', display: 'inline-flex'}}>
<Icon color={Colors.Gray400} name="asset_check" size={16} />
<Box style={{flex: 1}} flex={{wrap: 'wrap', display: 'inline-flex'}}>
{`${assetChecks.length} asset checks`}
</Box>
</Box>
</ButtonLink>
)}
</TagActionsPopover>
{dialog}
</span>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {Link, useLocation} from 'react-router-dom';
import {assertUnreachable} from '../app/Util';
import {PythonErrorFragment} from '../app/types/PythonErrorFragment.types';
import {displayNameForAssetKey} from '../asset-graph/Utils';
import {assetDetailsPathForKey} from '../assets/assetDetailsPathForKey';
import {
assetDetailsPathForAssetCheck,
assetDetailsPathForKey,
} from '../assets/assetDetailsPathForKey';
import {AssetKey} from '../assets/types';
import {ErrorSource, DagsterEventType} from '../graphql/types';
import {
Expand Down Expand Up @@ -430,11 +433,7 @@ const AssetCheckEvaluationContent: React.FC<{
}> = ({node, eventType}) => {
const {checkName, success, metadataEntries, targetMaterialization, assetKey} = node.evaluation;

const checkLink = assetDetailsPathForKey(assetKey, {
view: 'checks',
checkDetail: checkName,
});

const checkLink = assetDetailsPathForAssetCheck({assetKey, name: checkName});
const matLink = assetDetailsPathForKey(assetKey, {
view: 'events',
asOf: targetMaterialization ? `${targetMaterialization.timestamp}` : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export const RUN_FRAGMENT = gql`
path
}
}
assetCheckSelection {
name
assetKey {
path
}
}
pipelineSnapshotId
executionPlan {
artifactsPersisted
Expand Down
3 changes: 2 additions & 1 deletion js_modules/dagster-ui/packages/ui-core/src/runs/RunRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {isThisThingAJob} from '../workspace/WorkspaceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {useRepositoryForRunWithParentSnapshot} from '../workspace/useRepositoryForRun';

import {AssetKeyTagCollection} from './AssetKeyTagCollection';
import {AssetKeyTagCollection, AssetCheckTagCollection} from './AssetTagCollections';
import {Run} from './Run';
import {RunConfigDialog} from './RunConfigDialog';
import {RUN_PAGE_FRAGMENT} from './RunFragments';
Expand Down Expand Up @@ -90,6 +90,7 @@ export const RunRoot = () => {
/>
</Tag>
) : null}
<AssetCheckTagCollection useTags assetChecks={run.assetCheckSelection} />
<AssetKeyTagCollection
useTags
assetKeys={
Expand Down
13 changes: 11 additions & 2 deletions js_modules/dagster-ui/packages/ui-core/src/runs/RunTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {AnchorButton} from '../ui/AnchorButton';
import {useRepositoryForRunWithoutSnapshot} from '../workspace/useRepositoryForRun';
import {workspacePipelinePath, workspacePipelinePathGuessRepo} from '../workspace/workspacePath';

import {AssetKeyTagCollection} from './AssetKeyTagCollection';
import {AssetKeyTagCollection, AssetCheckTagCollection} from './AssetTagCollections';
import {RunActionsMenu, RunBulkActionsMenu} from './RunActionsMenu';
import {RunCreatedByCell} from './RunCreatedByCell';
import {RunStatusTagWithStats} from './RunStatusTag';
Expand Down Expand Up @@ -231,6 +231,12 @@ export const RUN_TABLE_RUN_FRAGMENT = gql`
path
}
}
assetCheckSelection {
name
assetKey {
path
}
}
status
tags {
...RunTagsFragment
Expand Down Expand Up @@ -400,7 +406,10 @@ const RunRow: React.FC<{
<td style={{position: 'relative'}}>
<Box flex={{direction: 'column', gap: 5}}>
{isHiddenAssetGroupJob(run.pipelineName) ? (
<AssetKeyTagCollection assetKeys={assetKeysForRun(run)} />
<>
<AssetCheckTagCollection assetChecks={run.assetCheckSelection} />
<AssetKeyTagCollection assetKeys={assetKeysForRun(run)} />
</>
) : (
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
<PipelineReference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react';
import {MemoryRouter} from 'react-router-dom';

import {displayNameForAssetKey} from '../../asset-graph/Utils';
import {AssetKeyTagCollection} from '../AssetKeyTagCollection';
import {AssetKeyTagCollection} from '../AssetTagCollections';

describe('AssetKeyTagCollection', () => {
const makeKeys = (count: number) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('RunActionsMenu', () => {
},
solidSelection: null,
assetSelection: null,
assetCheckSelection: null,
tags: [],
startTime: 123,
endTime: 456,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 71ee0aa

Please sign in to comment.