Skip to content

Commit b46088f

Browse files
committed
[pac] frontend
1 parent b813da0 commit b46088f

22 files changed

Lines changed: 1158 additions & 11 deletions

js_modules/dagster-ui/packages/ui-core/client.json

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckDetailDialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const ASSET_CHECK_EXECUTION_FRAGMENT = gql`
9090
metadataEntries {
9191
...MetadataEntryFragment
9292
}
93+
partition
9394
}
9495
}
9596
${METADATA_ENTRY_FRAGMENT}

js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckExecutionList.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ type Execution = AssetCheckDetailsQuery['assetCheckExecutions'][0];
2222
export const AssetCheckExecutionList = ({
2323
executions,
2424
paginationProps,
25+
hasPartitions = false,
2526
}: {
2627
executions: Execution[];
2728
paginationProps: CursorPaginationProps;
29+
hasPartitions?: boolean;
2830
}) => {
2931
if (!executions) {
3032
return (
@@ -43,6 +45,7 @@ export const AssetCheckExecutionList = ({
4345
<th style={{width: '160px'}}>Evaluation result</th>
4446
<th style={{width: '200px'}}>Timestamp</th>
4547
<th style={{width: '200px'}}>Target materialization</th>
48+
{hasPartitions && <th style={{width: '150px'}}>Partition</th>}
4649
<th>
4750
<Box flex={{gap: 4}}>
4851
Description
@@ -95,6 +98,11 @@ export const AssetCheckExecutionList = ({
9598
' - '
9699
)}
97100
</td>
101+
{hasPartitions && (
102+
<td>
103+
{execution.evaluation?.partition ? execution.evaluation.partition : ' - '}
104+
</td>
105+
)}
98106
<td>
99107
{execution.evaluation?.description ? (
100108
<Description fontSize="14px" description={execution.evaluation.description} />

js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckOverview.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ export const AssetCheckOverview = ({
136136
</Link>
137137
</Box>
138138
) : null}
139+
{lastExecution ? (
140+
<Box flex={{direction: 'column', gap: 8}}>
141+
<Subtitle2>Partition</Subtitle2>
142+
{lastExecution.evaluation?.partition ? (
143+
<div style={{fontFamily: FontFamily.monospace, fontSize: 12}}>
144+
{lastExecution.evaluation.partition}
145+
</div>
146+
) : (
147+
<>&mdash;</>
148+
)}
149+
</Box>
150+
) : null}
139151
</Box>
140152
{lastExecution?.evaluation?.metadataEntries.length ? (
141153
<Box flex={{direction: 'column', gap: 8}}>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import {Box, Heading, NonIdealState, Spinner, Tag} from '@dagster-io/ui-components';
2+
import {useMemo} from 'react';
3+
import {Link} from 'react-router-dom';
4+
5+
import {AssetCheckPartitionStatus} from './AssetCheckPartitionStatus';
6+
import {AssetCheckStatusTag} from './AssetCheckStatusTag';
7+
import {useAssetCheckPartitionDetail} from './useAssetCheckPartitionDetail';
8+
import {MetadataEntries} from '../../metadata/MetadataEntry';
9+
import {linkToRunEvent} from '../../runs/RunUtils';
10+
import {TimestampDisplay} from '../../schedules/TimestampDisplay';
11+
import {AssetKey} from '../types';
12+
13+
interface AssetCheckPartitionDetailProps {
14+
assetKey: AssetKey;
15+
checkName: string;
16+
partitionKey: string;
17+
}
18+
19+
export const AssetCheckPartitionDetail = ({
20+
assetKey,
21+
checkName,
22+
partitionKey,
23+
}: AssetCheckPartitionDetailProps) => {
24+
const {data: executionData, loading: executionLoading} = useAssetCheckPartitionDetail(
25+
assetKey,
26+
checkName,
27+
partitionKey,
28+
);
29+
30+
const latestExecution = useMemo(() => {
31+
if (!executionData?.assetCheckExecutions?.length) {
32+
return null;
33+
}
34+
35+
return executionData.assetCheckExecutions[0];
36+
}, [executionData]);
37+
38+
const partitionStatus = useMemo(() => {
39+
if (!latestExecution) {
40+
return AssetCheckPartitionStatus.MISSING;
41+
}
42+
return latestExecution.status;
43+
}, [latestExecution]);
44+
45+
if (executionLoading || !executionData) {
46+
return (
47+
<Box
48+
flex={{direction: 'column', justifyContent: 'center', alignItems: 'center'}}
49+
style={{height: '100%'}}
50+
>
51+
<Spinner purpose="section" />
52+
</Box>
53+
);
54+
}
55+
56+
if (!partitionStatus) {
57+
return (
58+
<Box padding={24}>
59+
<NonIdealState
60+
title="Partition not found"
61+
description={`No data available for partition "${partitionKey}"`}
62+
icon="partition"
63+
/>
64+
</Box>
65+
);
66+
}
67+
68+
const primaryStatus = partitionStatus;
69+
70+
// Handle missing status with a blank state
71+
if (primaryStatus === AssetCheckPartitionStatus.MISSING) {
72+
return (
73+
<Box padding={24}>
74+
<Box flex={{direction: 'column', gap: 16}}>
75+
<Heading>{partitionKey}</Heading>
76+
77+
<Box flex={{direction: 'column', gap: 12}}>
78+
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
79+
<strong>Status:</strong>
80+
<Tag intent="none">No execution attempted</Tag>
81+
</Box>
82+
83+
<Box flex={{direction: 'column', gap: 8}}>
84+
<Box style={{color: '#666', fontSize: '14px'}}>
85+
This partition has not been executed for this asset check yet.
86+
</Box>
87+
</Box>
88+
</Box>
89+
</Box>
90+
</Box>
91+
);
92+
}
93+
94+
return (
95+
<Box padding={24}>
96+
<Box flex={{direction: 'column', gap: 16}}>
97+
<Heading>{partitionKey}</Heading>
98+
99+
<Box flex={{direction: 'column', gap: 12}}>
100+
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
101+
<strong>Status:</strong>
102+
{latestExecution ? (
103+
<AssetCheckStatusTag execution={latestExecution} />
104+
) : (
105+
<Tag intent="none">No execution data</Tag>
106+
)}
107+
</Box>
108+
109+
{executionLoading ? (
110+
<Box flex={{direction: 'row', alignItems: 'center', gap: 8}}>
111+
<Spinner purpose="body-text" />
112+
<span>Loading execution details...</span>
113+
</Box>
114+
) : latestExecution ? (
115+
<Box flex={{direction: 'column', gap: 12}}>
116+
{/* Execution timestamp */}
117+
{latestExecution.evaluation?.timestamp && (
118+
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
119+
<strong>Executed:</strong>
120+
<Link
121+
to={linkToRunEvent(
122+
{id: latestExecution.runId},
123+
{stepKey: latestExecution.stepKey, timestamp: latestExecution.timestamp},
124+
)}
125+
>
126+
<TimestampDisplay timestamp={latestExecution.evaluation.timestamp} />
127+
</Link>
128+
</Box>
129+
)}
130+
131+
{/* Target materialization */}
132+
{latestExecution.evaluation?.targetMaterialization && (
133+
<Box flex={{direction: 'row', gap: 8, alignItems: 'center'}}>
134+
<strong>Target materialization:</strong>
135+
<Link to={`/runs/${latestExecution.evaluation.targetMaterialization.runId}`}>
136+
<TimestampDisplay
137+
timestamp={latestExecution.evaluation.targetMaterialization.timestamp}
138+
/>
139+
</Link>
140+
</Box>
141+
)}
142+
143+
{/* Description */}
144+
{latestExecution.evaluation?.description && (
145+
<Box flex={{direction: 'column', gap: 4}}>
146+
<strong>Description:</strong>
147+
<Box style={{color: '#666'}}>{latestExecution.evaluation.description}</Box>
148+
</Box>
149+
)}
150+
151+
{/* Metadata */}
152+
{latestExecution.evaluation?.metadataEntries &&
153+
latestExecution.evaluation.metadataEntries.length > 0 && (
154+
<Box flex={{direction: 'column', gap: 4}}>
155+
<strong>Metadata:</strong>
156+
<MetadataEntries entries={latestExecution.evaluation.metadataEntries} />
157+
</Box>
158+
)}
159+
</Box>
160+
) : (
161+
<Box style={{color: '#666', fontSize: '14px'}}>
162+
No execution data available for this partition.
163+
</Box>
164+
)}
165+
</Box>
166+
</Box>
167+
</Box>
168+
);
169+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {Colors} from '@dagster-io/ui-components';
2+
import {CSSProperties} from 'react';
3+
4+
import {assertUnreachable} from '../../app/Util';
5+
import {AssetCheckExecutionResolvedStatus} from '../../graphql/types';
6+
7+
// Asset Check Partition Status includes all execution statuses plus MISSING for unattempted partitions
8+
export enum AssetCheckPartitionStatus {
9+
MISSING = 'MISSING',
10+
IN_PROGRESS = 'IN_PROGRESS',
11+
SUCCEEDED = 'SUCCEEDED',
12+
FAILED = 'FAILED',
13+
EXECUTION_FAILED = 'EXECUTION_FAILED',
14+
SKIPPED = 'SKIPPED',
15+
}
16+
17+
export const emptyAssetCheckPartitionStatusCounts = () => ({
18+
[AssetCheckPartitionStatus.MISSING]: 0,
19+
[AssetCheckPartitionStatus.IN_PROGRESS]: 0,
20+
[AssetCheckPartitionStatus.SUCCEEDED]: 0,
21+
[AssetCheckPartitionStatus.FAILED]: 0,
22+
[AssetCheckPartitionStatus.EXECUTION_FAILED]: 0,
23+
[AssetCheckPartitionStatus.SKIPPED]: 0,
24+
});
25+
26+
export const assetCheckPartitionStatusToText = (status: AssetCheckPartitionStatus) => {
27+
switch (status) {
28+
case AssetCheckPartitionStatus.MISSING:
29+
return 'Missing';
30+
case AssetCheckPartitionStatus.SUCCEEDED:
31+
return 'Passed';
32+
case AssetCheckPartitionStatus.IN_PROGRESS:
33+
return 'In Progress';
34+
case AssetCheckPartitionStatus.FAILED:
35+
return 'Failed';
36+
case AssetCheckPartitionStatus.EXECUTION_FAILED:
37+
return 'Execution Failed';
38+
case AssetCheckPartitionStatus.SKIPPED:
39+
return 'Skipped';
40+
default:
41+
assertUnreachable(status);
42+
}
43+
};
44+
45+
const assetCheckPartitionStatusToColor = (status: AssetCheckPartitionStatus) => {
46+
switch (status) {
47+
case AssetCheckPartitionStatus.MISSING:
48+
return Colors.accentGray();
49+
case AssetCheckPartitionStatus.SUCCEEDED:
50+
return Colors.accentGreen();
51+
case AssetCheckPartitionStatus.IN_PROGRESS:
52+
return Colors.accentBlue();
53+
case AssetCheckPartitionStatus.FAILED:
54+
case AssetCheckPartitionStatus.EXECUTION_FAILED:
55+
return Colors.accentRed();
56+
case AssetCheckPartitionStatus.SKIPPED:
57+
return Colors.accentYellow();
58+
default:
59+
assertUnreachable(status);
60+
}
61+
};
62+
63+
export function assetCheckPartitionStatusesToStyle(
64+
statuses: AssetCheckPartitionStatus[],
65+
): CSSProperties {
66+
if (statuses.length === 1) {
67+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68+
return {backgroundColor: assetCheckPartitionStatusToColor(statuses[0]!)};
69+
}
70+
const colors = statuses.map(assetCheckPartitionStatusToColor);
71+
const pct = 100 / colors.length;
72+
const stops = colors.map((c, idx) => `${c} ${idx * pct}%, ${c} ${(idx + 1) * pct}%`);
73+
return {
74+
background: `linear-gradient(to right, ${stops.join(', ')})`,
75+
};
76+
}
77+
78+
// Helper function to convert AssetCheckExecutionResolvedStatus to AssetCheckPartitionStatus
79+
export function executionStatusToPartitionStatus(
80+
status: AssetCheckExecutionResolvedStatus,
81+
): AssetCheckPartitionStatus {
82+
switch (status) {
83+
case AssetCheckExecutionResolvedStatus.IN_PROGRESS:
84+
return AssetCheckPartitionStatus.IN_PROGRESS;
85+
case AssetCheckExecutionResolvedStatus.SUCCEEDED:
86+
return AssetCheckPartitionStatus.SUCCEEDED;
87+
case AssetCheckExecutionResolvedStatus.FAILED:
88+
return AssetCheckPartitionStatus.FAILED;
89+
case AssetCheckExecutionResolvedStatus.EXECUTION_FAILED:
90+
return AssetCheckPartitionStatus.EXECUTION_FAILED;
91+
case AssetCheckExecutionResolvedStatus.SKIPPED:
92+
return AssetCheckPartitionStatus.SKIPPED;
93+
default:
94+
assertUnreachable(status);
95+
}
96+
}
97+
98+
// Legacy exports for backward compatibility
99+
export const assetCheckExecutionStatusToText = assetCheckPartitionStatusToText;
100+
export const assetCheckExecutionStatusesToStyle = assetCheckPartitionStatusesToStyle;

0 commit comments

Comments
 (0)