Description
@@ -95,6 +98,11 @@ export const AssetCheckExecutionList = ({
' - '
)}
+ {hasPartitions && (
+ |
+ {execution.evaluation?.partition ? execution.evaluation.partition : ' - '}
+ |
+ )}
{execution.evaluation?.description ? (
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckOverview.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckOverview.tsx
index 9172b89d0a3fc..a4249bbebb6a8 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckOverview.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckOverview.tsx
@@ -136,6 +136,18 @@ export const AssetCheckOverview = ({
) : null}
+ {lastExecution ? (
+
+ Partition
+ {lastExecution.evaluation?.partition ? (
+
+ {lastExecution.evaluation.partition}
+
+ ) : (
+ <>—>
+ )}
+
+ ) : null}
{lastExecution?.evaluation?.metadataEntries.length ? (
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionDetail.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionDetail.tsx
new file mode 100644
index 0000000000000..71a122c401282
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionDetail.tsx
@@ -0,0 +1,169 @@
+import {Box, Heading, NonIdealState, Spinner, Tag} from '@dagster-io/ui-components';
+import {useMemo} from 'react';
+import {Link} from 'react-router-dom';
+
+import {AssetCheckPartitionStatus} from './AssetCheckPartitionStatus';
+import {AssetCheckStatusTag} from './AssetCheckStatusTag';
+import {useAssetCheckPartitionDetail} from './useAssetCheckPartitionDetail';
+import {MetadataEntries} from '../../metadata/MetadataEntry';
+import {linkToRunEvent} from '../../runs/RunUtils';
+import {TimestampDisplay} from '../../schedules/TimestampDisplay';
+import {AssetKey} from '../types';
+
+interface AssetCheckPartitionDetailProps {
+ assetKey: AssetKey;
+ checkName: string;
+ partitionKey: string;
+}
+
+export const AssetCheckPartitionDetail = ({
+ assetKey,
+ checkName,
+ partitionKey,
+}: AssetCheckPartitionDetailProps) => {
+ const {data: executionData, loading: executionLoading} = useAssetCheckPartitionDetail(
+ assetKey,
+ checkName,
+ partitionKey,
+ );
+
+ const latestExecution = useMemo(() => {
+ if (!executionData?.assetCheckExecutions?.length) {
+ return null;
+ }
+
+ return executionData.assetCheckExecutions[0];
+ }, [executionData]);
+
+ const partitionStatus = useMemo(() => {
+ if (!latestExecution) {
+ return AssetCheckPartitionStatus.MISSING;
+ }
+ return latestExecution.status;
+ }, [latestExecution]);
+
+ if (executionLoading || !executionData) {
+ return (
+
+
+
+ );
+ }
+
+ if (!partitionStatus) {
+ return (
+
+
+
+ );
+ }
+
+ const primaryStatus = partitionStatus;
+
+ // Handle missing status with a blank state
+ if (primaryStatus === AssetCheckPartitionStatus.MISSING) {
+ return (
+
+
+ {partitionKey}
+
+
+
+ Status:
+ No execution attempted
+
+
+
+
+ This partition has not been executed for this asset check yet.
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {partitionKey}
+
+
+
+ Status:
+ {latestExecution ? (
+
+ ) : (
+ No execution data
+ )}
+
+
+ {executionLoading ? (
+
+
+ Loading execution details...
+
+ ) : latestExecution ? (
+
+ {/* Execution timestamp */}
+ {latestExecution.evaluation?.timestamp && (
+
+ Executed:
+
+
+
+
+ )}
+
+ {/* Target materialization */}
+ {latestExecution.evaluation?.targetMaterialization && (
+
+ Target materialization:
+
+
+
+
+ )}
+
+ {/* Description */}
+ {latestExecution.evaluation?.description && (
+
+ Description:
+ {latestExecution.evaluation.description}
+
+ )}
+
+ {/* Metadata */}
+ {latestExecution.evaluation?.metadataEntries &&
+ latestExecution.evaluation.metadataEntries.length > 0 && (
+
+ Metadata:
+
+
+ )}
+
+ ) : (
+
+ No execution data available for this partition.
+
+ )}
+
+
+
+ );
+};
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionStatus.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionStatus.tsx
new file mode 100644
index 0000000000000..4597ed1d81abc
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitionStatus.tsx
@@ -0,0 +1,100 @@
+import {Colors} from '@dagster-io/ui-components';
+import {CSSProperties} from 'react';
+
+import {assertUnreachable} from '../../app/Util';
+import {AssetCheckExecutionResolvedStatus} from '../../graphql/types';
+
+// Asset Check Partition Status includes all execution statuses plus MISSING for unattempted partitions
+export enum AssetCheckPartitionStatus {
+ MISSING = 'MISSING',
+ IN_PROGRESS = 'IN_PROGRESS',
+ SUCCEEDED = 'SUCCEEDED',
+ FAILED = 'FAILED',
+ EXECUTION_FAILED = 'EXECUTION_FAILED',
+ SKIPPED = 'SKIPPED',
+}
+
+export const emptyAssetCheckPartitionStatusCounts = () => ({
+ [AssetCheckPartitionStatus.MISSING]: 0,
+ [AssetCheckPartitionStatus.IN_PROGRESS]: 0,
+ [AssetCheckPartitionStatus.SUCCEEDED]: 0,
+ [AssetCheckPartitionStatus.FAILED]: 0,
+ [AssetCheckPartitionStatus.EXECUTION_FAILED]: 0,
+ [AssetCheckPartitionStatus.SKIPPED]: 0,
+});
+
+export const assetCheckPartitionStatusToText = (status: AssetCheckPartitionStatus) => {
+ switch (status) {
+ case AssetCheckPartitionStatus.MISSING:
+ return 'Missing';
+ case AssetCheckPartitionStatus.SUCCEEDED:
+ return 'Passed';
+ case AssetCheckPartitionStatus.IN_PROGRESS:
+ return 'In Progress';
+ case AssetCheckPartitionStatus.FAILED:
+ return 'Failed';
+ case AssetCheckPartitionStatus.EXECUTION_FAILED:
+ return 'Execution Failed';
+ case AssetCheckPartitionStatus.SKIPPED:
+ return 'Skipped';
+ default:
+ assertUnreachable(status);
+ }
+};
+
+const assetCheckPartitionStatusToColor = (status: AssetCheckPartitionStatus) => {
+ switch (status) {
+ case AssetCheckPartitionStatus.MISSING:
+ return Colors.accentGray();
+ case AssetCheckPartitionStatus.SUCCEEDED:
+ return Colors.accentGreen();
+ case AssetCheckPartitionStatus.IN_PROGRESS:
+ return Colors.accentBlue();
+ case AssetCheckPartitionStatus.FAILED:
+ case AssetCheckPartitionStatus.EXECUTION_FAILED:
+ return Colors.accentRed();
+ case AssetCheckPartitionStatus.SKIPPED:
+ return Colors.accentYellow();
+ default:
+ assertUnreachable(status);
+ }
+};
+
+export function assetCheckPartitionStatusesToStyle(
+ statuses: AssetCheckPartitionStatus[],
+): CSSProperties {
+ if (statuses.length === 1) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return {backgroundColor: assetCheckPartitionStatusToColor(statuses[0]!)};
+ }
+ const colors = statuses.map(assetCheckPartitionStatusToColor);
+ const pct = 100 / colors.length;
+ const stops = colors.map((c, idx) => `${c} ${idx * pct}%, ${c} ${(idx + 1) * pct}%`);
+ return {
+ background: `linear-gradient(to right, ${stops.join(', ')})`,
+ };
+}
+
+// Helper function to convert AssetCheckExecutionResolvedStatus to AssetCheckPartitionStatus
+export function executionStatusToPartitionStatus(
+ status: AssetCheckExecutionResolvedStatus,
+): AssetCheckPartitionStatus {
+ switch (status) {
+ case AssetCheckExecutionResolvedStatus.IN_PROGRESS:
+ return AssetCheckPartitionStatus.IN_PROGRESS;
+ case AssetCheckExecutionResolvedStatus.SUCCEEDED:
+ return AssetCheckPartitionStatus.SUCCEEDED;
+ case AssetCheckExecutionResolvedStatus.FAILED:
+ return AssetCheckPartitionStatus.FAILED;
+ case AssetCheckExecutionResolvedStatus.EXECUTION_FAILED:
+ return AssetCheckPartitionStatus.EXECUTION_FAILED;
+ case AssetCheckExecutionResolvedStatus.SKIPPED:
+ return AssetCheckPartitionStatus.SKIPPED;
+ default:
+ assertUnreachable(status);
+ }
+}
+
+// Legacy exports for backward compatibility
+export const assetCheckExecutionStatusToText = assetCheckPartitionStatusToText;
+export const assetCheckExecutionStatusesToStyle = assetCheckPartitionStatusesToStyle;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitions.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitions.tsx
new file mode 100644
index 0000000000000..2e1af3474d0ac
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckPartitions.tsx
@@ -0,0 +1,185 @@
+import {Box, Colors, NonIdealState, TextInput} from '@dagster-io/ui-components';
+import {useMemo, useState} from 'react';
+
+import {AssetCheckPartitionDetail} from './AssetCheckPartitionDetail';
+import {
+ AssetCheckPartitionStatus,
+ assetCheckPartitionStatusToText,
+ assetCheckPartitionStatusesToStyle,
+} from './AssetCheckPartitionStatus';
+import {useAssetCheckPartitionData} from './useAssetCheckPartitionData';
+import {useQueryPersistedState} from '../../hooks/useQueryPersistedState';
+import {testId} from '../../testing/testId';
+import {PartitionListSelector} from '../PartitionListSelector';
+import {PartitionStatusCheckboxes} from '../PartitionStatusCheckboxes';
+import {AssetKey} from '../types';
+
+interface AssetCheckPartitionsProps {
+ assetKey: AssetKey;
+ checkName: string;
+}
+
+const DISPLAYED_STATUSES: AssetCheckPartitionStatus[] = [
+ AssetCheckPartitionStatus.MISSING,
+ AssetCheckPartitionStatus.SKIPPED,
+ AssetCheckPartitionStatus.IN_PROGRESS,
+ AssetCheckPartitionStatus.SUCCEEDED,
+ AssetCheckPartitionStatus.FAILED,
+ AssetCheckPartitionStatus.EXECUTION_FAILED,
+].sort();
+
+const STATUS_ORDER: AssetCheckPartitionStatus[] = [
+ AssetCheckPartitionStatus.MISSING,
+ AssetCheckPartitionStatus.FAILED,
+ AssetCheckPartitionStatus.EXECUTION_FAILED,
+ AssetCheckPartitionStatus.SKIPPED,
+ AssetCheckPartitionStatus.IN_PROGRESS,
+ AssetCheckPartitionStatus.SUCCEEDED,
+];
+
+export const AssetCheckPartitions = ({assetKey, checkName}: AssetCheckPartitionsProps) => {
+ const {data: partitionData, loading} = useAssetCheckPartitionData(assetKey, checkName);
+ const [focusedPartitionKey, setFocusedPartitionKey] = useState();
+ const [searchValue, setSearchValue] = useState('');
+
+ const [statusFilters, setStatusFilters] = useQueryPersistedState({
+ defaults: {status: [...DISPLAYED_STATUSES].sort().join(',')},
+ encode: (val) => ({status: [...val].sort().join(',')}),
+ decode: (qs) => {
+ const status = qs.status;
+ if (typeof status === 'string') {
+ return status
+ .split(',')
+ .filter((s): s is AssetCheckPartitionStatus =>
+ DISPLAYED_STATUSES.includes(s as AssetCheckPartitionStatus),
+ );
+ }
+ return [...DISPLAYED_STATUSES];
+ },
+ });
+
+ // Calculate partition counts by status
+ const {filteredPartitions, countsByStatus} = useMemo(() => {
+ if (!partitionData) {
+ return {filteredPartitions: [], countsByStatus: {}};
+ }
+
+ const counts: {[key: string]: number} = {
+ [AssetCheckPartitionStatus.MISSING]: 0,
+ [AssetCheckPartitionStatus.SKIPPED]: 0,
+ [AssetCheckPartitionStatus.SUCCEEDED]: 0,
+ [AssetCheckPartitionStatus.IN_PROGRESS]: 0,
+ [AssetCheckPartitionStatus.FAILED]: 0,
+ [AssetCheckPartitionStatus.EXECUTION_FAILED]: 0,
+ };
+
+ const allPartitions = partitionData.partitions;
+ const filtered = allPartitions.filter((partition) => {
+ if (searchValue && !partition.toLowerCase().includes(searchValue.toLowerCase())) {
+ return false;
+ }
+
+ const primaryStatus = partitionData.statusForPartition(partition);
+
+ counts[primaryStatus] = !counts[primaryStatus] ? 1 : counts[primaryStatus] + 1;
+ return statusFilters.includes(primaryStatus);
+ });
+
+ return {filteredPartitions: filtered, countsByStatus: counts};
+ }, [partitionData, statusFilters, searchValue]);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!partitionData || partitionData.partitions.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ const statusForPartition = (dimensionKey: string): AssetCheckPartitionStatus[] => {
+ return [partitionData.statusForPartition(dimensionKey)];
+ };
+
+ return (
+ <>
+
+
+ {filteredPartitions.length.toLocaleString()} Partitions Selected
+
+
+
+
+
+
+
+ setSearchValue(e.target.value)}
+ placeholder="Filter partitions"
+ />
+
+
+
+
+
+
+ {focusedPartitionKey ? (
+
+ ) : (
+
+ Select a partition to view details
+
+ )}
+
+
+ >
+ );
+};
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecks.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecks.tsx
index 0139460f0587e..3b2eb954bd049 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecks.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecks.tsx
@@ -24,6 +24,7 @@ import {
} from './AssetCheckDetailDialog';
import {AssetCheckExecutionList} from './AssetCheckExecutionList';
import {AssetCheckOverview} from './AssetCheckOverview';
+import {AssetCheckPartitions} from './AssetCheckPartitions';
import {ASSET_CHECKS_QUERY} from './AssetChecksQuery';
import {ExecuteChecksButton} from './ExecuteChecksButton';
import {
@@ -103,6 +104,10 @@ export const AssetChecks = ({
return checks.find((check) => check.name === selectedCheckName) ?? checks[0];
}, [selectedCheckName, checks]);
+ const hasPartitions = React.useMemo(() => {
+ return !!(selectedCheck && selectedCheck.partitionDefinition);
+ }, [selectedCheck]);
+
const isSelectedCheckAutomated = !!selectedCheck?.automationCondition;
const {paginationProps, executions, executionsLoading} = useHistoricalCheckExecutions(
@@ -243,6 +248,7 @@ export const AssetChecks = ({
{
setActiveTab(tab);
}}
@@ -258,11 +264,18 @@ export const AssetChecks = ({
/>
) : null}
{activeTab === 'execution-history' ? (
-
+
) : null}
{activeTab === 'automation-history' ? (
) : null}
+ {activeTab === 'partitions' ? (
+
+ ) : null}
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksQuery.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksQuery.tsx
index 1be396c818b8c..081c5e798260f 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksQuery.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksQuery.tsx
@@ -14,6 +14,18 @@ const ASSET_CHECK_KEY_FRAGMENT = gql`
}
`;
+export const ASSET_CHECK_PARTITION_FRAGMENT = gql`
+ fragment AssetCheckPartitionFragment on AssetCheck {
+ partitionDefinition {
+ description
+ dimensionTypes {
+ type
+ dynamicPartitionsDefinitionName
+ }
+ }
+ }
+`;
+
export const ASSET_CHECKS_QUERY = gql`
query AssetChecksQuery($assetKey: AssetKeyInput!) {
assetNodeOrError(assetKey: $assetKey) {
@@ -30,6 +42,7 @@ export const ASSET_CHECKS_QUERY = gql`
...AssetCheckKeyFragment
...ExecuteChecksButtonCheckFragment
...AssetCheckTableFragment
+ ...AssetCheckPartitionFragment
}
}
}
@@ -41,4 +54,5 @@ export const ASSET_CHECKS_QUERY = gql`
${EXECUTE_CHECKS_BUTTON_CHECK_FRAGMENT}
${ASSET_CHECK_TABLE_FRAGMENT}
${ASSET_CHECK_KEY_FRAGMENT}
+ ${ASSET_CHECK_PARTITION_FRAGMENT}
`;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksTabs.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksTabs.tsx
index d7264c45d3cdd..6ac395c86c009 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksTabs.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetChecksTabs.tsx
@@ -1,14 +1,24 @@
import {Tab, Tabs, Tooltip} from '@dagster-io/ui-components';
-export type AssetChecksTabType = 'overview' | 'execution-history' | 'automation-history';
+export type AssetChecksTabType =
+ | 'overview'
+ | 'execution-history'
+ | 'automation-history'
+ | 'partitions';
interface Props {
activeTab: AssetChecksTabType;
enableAutomationHistory: boolean;
onChange: (tabId: AssetChecksTabType) => void;
+ hasPartitions: boolean;
}
-export const AssetChecksTabs = ({activeTab, enableAutomationHistory, onChange}: Props) => {
+export const AssetChecksTabs = ({
+ activeTab,
+ enableAutomationHistory,
+ onChange,
+ hasPartitions,
+}: Props) => {
return (
@@ -26,6 +36,7 @@ export const AssetChecksTabs = ({activeTab, enableAutomationHistory, onChange}:
}
disabled={!enableAutomationHistory}
/>
+ {hasPartitions && }
);
};
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckDetailDialog.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckDetailDialog.types.ts
index 83eb2aec83a21..0f537457572ed 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckDetailDialog.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckDetailDialog.types.ts
@@ -20,6 +20,7 @@ export type AssetCheckExecutionFragment = {
severity: Types.AssetCheckSeverity;
timestamp: number;
description: string | null;
+ partition: string | null;
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
timestamp: number;
@@ -202,6 +203,7 @@ export type AssetCheckDetailsQuery = {
severity: Types.AssetCheckSeverity;
timestamp: number;
description: string | null;
+ partition: string | null;
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
timestamp: number;
@@ -364,4 +366,4 @@ export type AssetCheckDetailsQuery = {
}>;
};
-export const AssetCheckDetailsQueryVersion = 'dafb023319ef2c8aa8075c43686b318d3206f5781b8b87cf21130db3172a9f71';
+export const AssetCheckDetailsQueryVersion = 'f5e3bf07865d4121f8ab8e2e31c9af1519f915dfbfec0daa941de352610a4673';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetChecksQuery.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetChecksQuery.types.ts
index 3bdff0cf063f8..aca7bb9b58724 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetChecksQuery.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetChecksQuery.types.ts
@@ -8,6 +8,19 @@ export type AssetCheckKeyFragment = {
assetKey: {__typename: 'AssetKey'; path: Array};
};
+export type AssetCheckPartitionFragment = {
+ __typename: 'AssetCheck';
+ partitionDefinition: {
+ __typename: 'PartitionDefinition';
+ description: string;
+ dimensionTypes: Array<{
+ __typename: 'DimensionDefinitionType';
+ type: Types.PartitionDefinitionType;
+ dynamicPartitionsDefinitionName: string | null;
+ }>;
+ } | null;
+};
+
export type AssetChecksQueryVariables = Types.Exact<{
assetKey: Types.AssetKeyInput;
}>;
@@ -50,6 +63,7 @@ export type AssetChecksQuery = {
severity: Types.AssetCheckSeverity;
timestamp: number;
description: string | null;
+ partition: string | null;
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
timestamp: number;
@@ -244,6 +258,15 @@ export type AssetChecksQuery = {
>;
} | null;
} | null;
+ partitionDefinition: {
+ __typename: 'PartitionDefinition';
+ description: string;
+ dimensionTypes: Array<{
+ __typename: 'DimensionDefinitionType';
+ type: Types.PartitionDefinitionType;
+ dynamicPartitionsDefinitionName: string | null;
+ }>;
+ } | null;
}>;
};
assetKey: {__typename: 'AssetKey'; path: Array};
@@ -257,4 +280,4 @@ export type AssetChecksQuery = {
| {__typename: 'AssetNotFoundError'};
};
-export const AssetChecksQueryVersion = 'df2f0eba0bb9816ab3ffabe630b837f47a61ae541a1b543bd7abf365b0754094';
+export const AssetChecksQueryVersion = '0161481af04ecfb7dd909572994e065f7ec758af1b61e3bd825e494702063ecf';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/VirtualizedAssetCheckTable.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/VirtualizedAssetCheckTable.types.ts
index 0211161861c7a..53fe779e0c050 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/VirtualizedAssetCheckTable.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/VirtualizedAssetCheckTable.types.ts
@@ -26,6 +26,7 @@ export type AssetCheckTableFragment = {
severity: Types.AssetCheckSeverity;
timestamp: number;
description: string | null;
+ partition: string | null;
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
timestamp: number;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionData.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionData.types.ts
new file mode 100644
index 0000000000000..2729cf6042a28
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionData.types.ts
@@ -0,0 +1,43 @@
+// Generated GraphQL types, do not edit manually.
+
+import * as Types from '../../../graphql/types';
+
+export type AssetCheckPartitionHealthQueryVariables = Types.Exact<{
+ assetKey: Types.AssetKeyInput;
+ checkName: Types.Scalars['String']['input'];
+}>;
+
+export type AssetCheckPartitionHealthQuery = {
+ __typename: 'Query';
+ assetNodeOrError:
+ | {
+ __typename: 'AssetNode';
+ id: string;
+ assetCheckOrError:
+ | {
+ __typename: 'AssetCheck';
+ name: string;
+ partitionKeysByDimension: Array<{
+ __typename: 'DimensionPartitionKeys';
+ name: string;
+ partitionKeys: Array;
+ }>;
+ partitionStatuses: {
+ __typename: 'AssetCheckPartitionStatuses';
+ missing: Array;
+ succeeded: Array;
+ failed: Array;
+ inProgress: Array;
+ skipped: Array;
+ executionFailed: Array;
+ } | null;
+ }
+ | {__typename: 'AssetCheckNeedsAgentUpgradeError'}
+ | {__typename: 'AssetCheckNeedsMigrationError'}
+ | {__typename: 'AssetCheckNeedsUserCodeUpgrade'}
+ | {__typename: 'AssetCheckNotFoundError'};
+ }
+ | {__typename: 'AssetNotFoundError'};
+};
+
+export const AssetCheckPartitionHealthQueryVersion = '85d84ebb07a4f45fa3c774e3c2bbaa51eeeb4215cd7a95ea374bd2ce9e10656a';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionDetail.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionDetail.types.ts
new file mode 100644
index 0000000000000..8434ee3ad2a7c
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/useAssetCheckPartitionDetail.types.ts
@@ -0,0 +1,189 @@
+// Generated GraphQL types, do not edit manually.
+
+import * as Types from '../../../graphql/types';
+
+export type AssetCheckPartitionDetailQueryVariables = Types.Exact<{
+ assetKey: Types.AssetKeyInput;
+ checkName: Types.Scalars['String']['input'];
+ partitionKey: Types.Scalars['String']['input'];
+}>;
+
+export type AssetCheckPartitionDetailQuery = {
+ __typename: 'Query';
+ assetCheckExecutions: Array<{
+ __typename: 'AssetCheckExecution';
+ id: string;
+ runId: string;
+ status: Types.AssetCheckExecutionResolvedStatus;
+ timestamp: number;
+ stepKey: string | null;
+ evaluation: {
+ __typename: 'AssetCheckEvaluation';
+ timestamp: number;
+ severity: Types.AssetCheckSeverity;
+ description: string | null;
+ success: boolean;
+ partition: string | null;
+ targetMaterialization: {
+ __typename: 'AssetCheckEvaluationTargetMaterializationData';
+ timestamp: number;
+ runId: string;
+ } | null;
+ metadataEntries: Array<
+ | {
+ __typename: 'AssetMetadataEntry';
+ label: string;
+ description: string | null;
+ assetKey: {__typename: 'AssetKey'; path: Array};
+ }
+ | {
+ __typename: 'BoolMetadataEntry';
+ boolValue: boolean | null;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'CodeReferencesMetadataEntry';
+ label: string;
+ description: string | null;
+ codeReferences: Array<
+ | {
+ __typename: 'LocalFileCodeReference';
+ filePath: string;
+ lineNumber: number | null;
+ label: string | null;
+ }
+ | {__typename: 'UrlCodeReference'; url: string; label: string | null}
+ >;
+ }
+ | {
+ __typename: 'FloatMetadataEntry';
+ floatValue: number | null;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'IntMetadataEntry';
+ intValue: number | null;
+ intRepr: string;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'JobMetadataEntry';
+ jobName: string;
+ repositoryName: string | null;
+ locationName: string;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'JsonMetadataEntry';
+ jsonString: string;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'MarkdownMetadataEntry';
+ mdStr: string;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'NotebookMetadataEntry';
+ path: string;
+ label: string;
+ description: string | null;
+ }
+ | {__typename: 'NullMetadataEntry'; label: string; description: string | null}
+ | {__typename: 'PathMetadataEntry'; path: string; label: string; description: string | null}
+ | {
+ __typename: 'PipelineRunMetadataEntry';
+ runId: string;
+ label: string;
+ description: string | null;
+ }
+ | {__typename: 'PoolMetadataEntry'; pool: string; label: string; description: string | null}
+ | {
+ __typename: 'PythonArtifactMetadataEntry';
+ module: string;
+ name: string;
+ label: string;
+ description: string | null;
+ }
+ | {
+ __typename: 'TableColumnLineageMetadataEntry';
+ label: string;
+ description: string | null;
+ lineage: Array<{
+ __typename: 'TableColumnLineageEntry';
+ columnName: string;
+ columnDeps: Array<{
+ __typename: 'TableColumnDep';
+ columnName: string;
+ assetKey: {__typename: 'AssetKey'; path: Array};
+ }>;
+ }>;
+ }
+ | {
+ __typename: 'TableMetadataEntry';
+ label: string;
+ description: string | null;
+ table: {
+ __typename: 'Table';
+ records: Array;
+ schema: {
+ __typename: 'TableSchema';
+ columns: Array<{
+ __typename: 'TableColumn';
+ name: string;
+ description: string | null;
+ type: string;
+ tags: Array<{__typename: 'DefinitionTag'; key: string; value: string}>;
+ constraints: {
+ __typename: 'TableColumnConstraints';
+ nullable: boolean;
+ unique: boolean;
+ other: Array;
+ };
+ }>;
+ constraints: {__typename: 'TableConstraints'; other: Array} | null;
+ };
+ };
+ }
+ | {
+ __typename: 'TableSchemaMetadataEntry';
+ label: string;
+ description: string | null;
+ schema: {
+ __typename: 'TableSchema';
+ columns: Array<{
+ __typename: 'TableColumn';
+ name: string;
+ description: string | null;
+ type: string;
+ tags: Array<{__typename: 'DefinitionTag'; key: string; value: string}>;
+ constraints: {
+ __typename: 'TableColumnConstraints';
+ nullable: boolean;
+ unique: boolean;
+ other: Array;
+ };
+ }>;
+ constraints: {__typename: 'TableConstraints'; other: Array} | null;
+ };
+ }
+ | {__typename: 'TextMetadataEntry'; text: string; label: string; description: string | null}
+ | {
+ __typename: 'TimestampMetadataEntry';
+ timestamp: number;
+ label: string;
+ description: string | null;
+ }
+ | {__typename: 'UrlMetadataEntry'; url: string; label: string; description: string | null}
+ >;
+ } | null;
+ }>;
+};
+
+export const AssetCheckPartitionDetailQueryVersion = '61a67dd1e835034edff4f8a722b9c6afb702eaaac5fdb2829f8e4a233a7832db';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionData.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionData.tsx
new file mode 100644
index 0000000000000..41051b7e84f19
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionData.tsx
@@ -0,0 +1,182 @@
+import {useMemo, useState} from 'react';
+
+import {AssetCheckPartitionStatus} from './AssetCheckPartitionStatus';
+import {gql, useApolloClient} from '../../apollo-client';
+import {usePartitionDataSubscriber} from '../PartitionSubscribers';
+import {AssetKey} from '../types';
+import {
+ AssetCheckPartitionHealthQuery,
+ AssetCheckPartitionHealthQueryVariables,
+} from './types/useAssetCheckPartitionData.types';
+
+export interface AssetCheckPartitionData {
+ assetKey: AssetKey;
+ checkName: string;
+ dimensions: AssetCheckPartitionDimension[];
+ partitions: string[];
+ statusForPartition: (dimensionKey: string) => AssetCheckPartitionStatus;
+}
+
+export interface AssetCheckPartitionDimension {
+ name: string;
+ partitionKeys: string[];
+}
+
+export function useAssetCheckPartitionData(assetKey: AssetKey | null, checkName: string | null) {
+ const [partitionsLastUpdated, setPartitionsLastUpdatedAt] = useState('');
+ usePartitionDataSubscriber(() => {
+ setPartitionsLastUpdatedAt(Date.now().toString());
+ });
+
+ const cacheKey = `${JSON.stringify(assetKey)}-${checkName}-${partitionsLastUpdated}`;
+ const [result, setResult] = useState<(AssetCheckPartitionData & {fetchedAt: string}) | null>(
+ null,
+ );
+ const client = useApolloClient();
+
+ const [loading, setLoading] = useState(true);
+
+ // Fetch partition data for the asset check
+ useMemo(() => {
+ if (!assetKey || !checkName) {
+ setLoading(false);
+ setResult(null);
+ return;
+ }
+
+ // Check if we already have this data cached
+ if (
+ result &&
+ result.assetKey === assetKey &&
+ result.checkName === checkName &&
+ result.fetchedAt === cacheKey
+ ) {
+ setLoading(false);
+ return;
+ }
+
+ const run = async () => {
+ try {
+ const {data} = await client.query<
+ AssetCheckPartitionHealthQuery,
+ AssetCheckPartitionHealthQueryVariables
+ >({
+ query: ASSET_CHECK_PARTITION_HEALTH_QUERY,
+ fetchPolicy: 'network-only',
+ variables: {
+ assetKey: {path: assetKey.path},
+ checkName,
+ },
+ });
+
+ const loaded = buildAssetCheckPartitionData(data, assetKey, checkName);
+ setResult(loaded ? {...loaded, fetchedAt: cacheKey} : null);
+ } catch (error) {
+ console.error('Failed to fetch asset check partition data:', error);
+ setResult(null);
+ }
+ setLoading(false);
+ };
+ run();
+ }, [client, assetKey, checkName, cacheKey, result]);
+
+ return {data: result, loading};
+}
+
+function buildAssetCheckPartitionData(
+ data: AssetCheckPartitionHealthQuery,
+ assetKey: AssetKey,
+ checkName: string,
+): AssetCheckPartitionData | null {
+ const assetNode = data.assetNodeOrError.__typename === 'AssetNode' ? data.assetNodeOrError : null;
+
+ if (!assetNode?.assetCheckOrError || assetNode.assetCheckOrError.__typename !== 'AssetCheck') {
+ return null;
+ }
+
+ const check = assetNode.assetCheckOrError;
+
+ const dimensions = (check.partitionKeysByDimension || []).map((dim) => ({
+ name: dim.name,
+ partitionKeys: dim.partitionKeys,
+ }));
+
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const dim = dimensions[0]!;
+ const partitions = dimensions.length > 0 ? dim.partitionKeys : [];
+
+ const partitionStatusMap = new Map();
+ const partitionStatuses = check.partitionStatuses;
+
+ // Initialize all partitions as missing
+ for (const partition of partitions) {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.MISSING);
+ }
+
+ // Update with actual statuses from GraphQL
+ if (partitionStatuses) {
+ // Process each status type
+ partitionStatuses.missing?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.MISSING);
+ });
+
+ partitionStatuses.succeeded?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.SUCCEEDED);
+ });
+
+ partitionStatuses.failed?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.FAILED);
+ });
+
+ partitionStatuses.inProgress?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.IN_PROGRESS);
+ });
+
+ partitionStatuses.skipped?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.SKIPPED);
+ });
+
+ partitionStatuses.executionFailed?.forEach((partition) => {
+ partitionStatusMap.set(partition, AssetCheckPartitionStatus.EXECUTION_FAILED);
+ });
+ }
+
+ const statusForPartition = (dimensionKey: string): AssetCheckPartitionStatus => {
+ return partitionStatusMap.get(dimensionKey) || AssetCheckPartitionStatus.MISSING;
+ };
+
+ return {
+ assetKey,
+ checkName,
+ dimensions,
+ partitions,
+ statusForPartition,
+ };
+}
+
+export const ASSET_CHECK_PARTITION_HEALTH_QUERY = gql`
+ query AssetCheckPartitionHealthQuery($assetKey: AssetKeyInput!, $checkName: String!) {
+ assetNodeOrError(assetKey: $assetKey) {
+ ... on AssetNode {
+ id
+ assetCheckOrError(checkName: $checkName) {
+ ... on AssetCheck {
+ name
+ partitionKeysByDimension {
+ name
+ partitionKeys
+ }
+ partitionStatuses {
+ missing
+ succeeded
+ failed
+ inProgress
+ skipped
+ executionFailed
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionDetail.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionDetail.tsx
new file mode 100644
index 0000000000000..44cb862ab334a
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/useAssetCheckPartitionDetail.tsx
@@ -0,0 +1,63 @@
+import {gql, useQuery} from '../../apollo-client';
+import {METADATA_ENTRY_FRAGMENT} from '../../metadata/MetadataEntryFragment';
+import {AssetKey} from '../types';
+import {
+ AssetCheckPartitionDetailQuery,
+ AssetCheckPartitionDetailQueryVariables,
+} from './types/useAssetCheckPartitionDetail.types';
+
+export function useAssetCheckPartitionDetail(
+ assetKey: AssetKey | null,
+ checkName: string | null,
+ partitionKey: string | null,
+) {
+ const queryResult = useQuery<
+ AssetCheckPartitionDetailQuery,
+ AssetCheckPartitionDetailQueryVariables
+ >(ASSET_CHECK_PARTITION_DETAIL_QUERY, {
+ variables: {
+ assetKey: assetKey ? {path: assetKey.path} : {path: []},
+ checkName: checkName || '',
+ partitionKey: partitionKey || '',
+ },
+ skip: !assetKey || !checkName || !partitionKey,
+ });
+
+ return queryResult;
+}
+
+export const ASSET_CHECK_PARTITION_DETAIL_QUERY = gql`
+ query AssetCheckPartitionDetailQuery(
+ $assetKey: AssetKeyInput!
+ $checkName: String!
+ $partitionKey: String!
+ ) {
+ assetCheckExecutions(
+ assetKey: $assetKey
+ checkName: $checkName
+ partition: $partitionKey
+ limit: 10
+ ) {
+ id
+ runId
+ status
+ timestamp
+ stepKey
+ evaluation {
+ timestamp
+ severity
+ description
+ success
+ partition
+ targetMaterialization {
+ timestamp
+ runId
+ }
+ metadataEntries {
+ ...MetadataEntryFragment
+ }
+ }
+ }
+ }
+ ${METADATA_ENTRY_FRAGMENT}
+`;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/LogsRow.tsx b/js_modules/dagster-ui/packages/ui-core/src/runs/LogsRow.tsx
index abfb0eabb2d70..5d6d20d4c2442 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/runs/LogsRow.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/runs/LogsRow.tsx
@@ -243,6 +243,7 @@ export const LOGS_ROW_STRUCTURED_FRAGMENT = gql`
metadataEntries {
...MetadataEntryFragment
}
+ partition
}
}
}
diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsProvider.types.ts b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsProvider.types.ts
index 8bc94cb57fadb..7dc030828e60c 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsProvider.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsProvider.types.ts
@@ -60,6 +60,7 @@ export type PipelineRunLogsSubscription = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
@@ -3915,6 +3916,7 @@ export type RunLogsSubscriptionSuccessFragment = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
@@ -7705,6 +7707,7 @@ export type RunLogsQuery = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
@@ -11517,6 +11520,6 @@ export type RunLogsQuery = {
| {__typename: 'RunNotFoundError'};
};
-export const PipelineRunLogsSubscriptionVersion = '929d969805de91b8890960433258984bbd32d2c08dbca55583da64c309fa3127';
+export const PipelineRunLogsSubscriptionVersion = '1fef76dfc3116df896a129a3e98e3071ced9a72b302b9e551dbcd5b28e7037cd';
-export const RunLogsQueryVersion = '811ef63a737599640cc1677f4a6c01c5193e5651e0b641680847abe613296deb';
+export const RunLogsQueryVersion = 'f81b9500cc7636e8bc477dbef44c955a449171ed951c2ffabd55c00f36aea8af';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsRow.types.ts b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsRow.types.ts
index 3ded08f9f5910..096afc7f153f0 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsRow.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsRow.types.ts
@@ -41,6 +41,7 @@ export type LogsRowStructuredFragment_AssetCheckEvaluationEvent = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsScrollingTableMessageFragment.types.ts b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsScrollingTableMessageFragment.types.ts
index 1b46382c9c9af..8d86b475dc75b 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsScrollingTableMessageFragment.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/runs/types/LogsScrollingTableMessageFragment.types.ts
@@ -41,6 +41,7 @@ export type LogsScrollingTableMessageFragment_AssetCheckEvaluationEvent = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/types/RunFragments.types.ts b/js_modules/dagster-ui/packages/ui-core/src/runs/types/RunFragments.types.ts
index 8ace6b46c7044..537e7ecfb655e 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/runs/types/RunFragments.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/runs/types/RunFragments.types.ts
@@ -92,6 +92,7 @@ export type RunDagsterRunEventFragment_AssetCheckEvaluationEvent = {
checkName: string;
success: boolean;
timestamp: number;
+ partition: string | null;
assetKey: {__typename: 'AssetKey'; path: Array};
targetMaterialization: {
__typename: 'AssetCheckEvaluationTargetMaterializationData';
|