diff --git a/js_modules/dagster-ui/packages/ui-core/client.json b/js_modules/dagster-ui/packages/ui-core/client.json index de992f056747a..62a4e28337304 100644 --- a/js_modules/dagster-ui/packages/ui-core/client.json +++ b/js_modules/dagster-ui/packages/ui-core/client.json @@ -36,8 +36,9 @@ "LaunchAssetCheckUpstreamQuery": "afb78499f0bf86942fc7f1ff7261c34caec2bd1e4aabb05c95a2db6acc811aaa", "OverduePopoverQuery": "3c8359e1adfab8237e4b26508489f07c09b24069373064c6c94d645312ae9296", "RunningBackfillsNoticeQuery": "edaaca1d6474672ae342eb3887f2aed16fbb502b704a603986d21f14bc10ee53", + "AssetCheckAutomationListQuery": "b6dffe3883c3c008672d9366595c876d95e0b1672f891695550ab513c93fa3a8", "AssetCheckDetailsQuery": "c448858a73cd66132c2d6f232497570d217ece793897c47aaa1155b15c7ef8e9", - "AssetChecksQuery": "2487c52958999bb33e5daa6c0caa0eea46ab200cb7d5d2294a8290e8e1ad3025", + "AssetChecksQuery": "67252db2bc1bfc878d1568008f7e0698a4515d15b4b68c8e88bd1edef4c1f60f", "AssetDaemonTicksQuery": "399ac77e660d40eba32c2ab06db2a2936a71e660d93ec108364eec1fdfc16788", "AssetColumnLineage": "bcb70460f77b88bbbfaec90982f3e99f522d9a4e270e63832684cfde169fabc7", "GetAutoMaterializePausedQuery": "50f74183f54031274136ab855701d01f26642a6d958d7452ae13aa6c40ca349d", diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckAutomationList.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckAutomationList.tsx new file mode 100644 index 0000000000000..83c2670e0a1ca --- /dev/null +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/AssetCheckAutomationList.tsx @@ -0,0 +1,118 @@ +import {Box, CursorHistoryControls} from '@dagster-io/ui-components'; +import {useMemo} from 'react'; + +import {EvaluationList} from '../AutoMaterializePolicyPage/EvaluationList'; +import {AssetKey} from '../types'; +import { + AssetCheckAutomationListQuery, + AssetCheckAutomationListQueryVariables, +} from './types/AssetCheckAutomationList.types'; +import {gql} from '../../apollo-client'; +import {useCursorPaginatedQuery} from '../../runs/useCursorPaginatedQuery'; +import {ASSET_CONDITION_EVALUATION_RECORD_FRAGMENT} from '../AutoMaterializePolicyPage/GetEvaluationsQuery'; +import {AssetCheckKeyFragment} from './types/AssetChecksQuery.types'; + +interface Props { + assetCheck: AssetCheckKeyFragment; + checkName: string; +} + +export const AssetCheckAutomationList = ({assetCheck, checkName}: Props) => { + const {queryResult, paginationProps} = useEvaluationsQueryResult({ + assetKey: assetCheck.assetKey, + checkName, + }); + + const evaluations = useMemo(() => { + if ( + queryResult?.data?.assetConditionEvaluationRecordsOrError?.__typename === + 'AssetConditionEvaluationRecords' + ) { + return queryResult.data.assetConditionEvaluationRecordsOrError.records; + } + return []; + }, [queryResult]); + + return ( + <> + + + + + + ); +}; + +export const PAGE_SIZE = 30; + +// This function exists mostly to use the return type later +export function useEvaluationsQueryResult({ + assetKey, + checkName, +}: { + assetKey: AssetKey; + checkName: string; +}) { + const result = useCursorPaginatedQuery< + AssetCheckAutomationListQuery, + AssetCheckAutomationListQueryVariables + >({ + nextCursorForResult: (data) => { + if ( + data.assetConditionEvaluationRecordsOrError?.__typename === + 'AssetConditionEvaluationRecords' + ) { + return data.assetConditionEvaluationRecordsOrError.records[PAGE_SIZE - 1]?.evaluationId; + } + return undefined; + }, + getResultArray: (data) => { + if ( + data?.assetConditionEvaluationRecordsOrError?.__typename === + 'AssetConditionEvaluationRecords' + ) { + return data.assetConditionEvaluationRecordsOrError.records; + } + return []; + }, + variables: { + assetCheckKey: { + assetKey: {path: assetKey.path}, + name: checkName, + }, + }, + query: ASSET_CHECK_AUTOMATION_LIST_QUERY, + pageSize: PAGE_SIZE, + }); + return result; +} + +const ASSET_CHECK_AUTOMATION_LIST_QUERY = gql` + query AssetCheckAutomationListQuery( + $assetCheckKey: AssetCheckHandleInput! + $limit: Int! + $cursor: String + ) { + assetConditionEvaluationRecordsOrError( + assetKey: null + assetCheckKey: $assetCheckKey + limit: $limit + cursor: $cursor + ) { + ... on AssetConditionEvaluationRecords { + records { + ...AssetConditionEvaluationRecordFragment + } + } + } + } + ${ASSET_CONDITION_EVALUATION_RECORD_FRAGMENT} +`; 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 b4ac9bcd1ee8f..2b9e6b661729b 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 @@ -15,6 +15,7 @@ import {useVirtualizer} from '@tanstack/react-virtual'; import React, {useMemo, useState} from 'react'; import styled from 'styled-components'; +import {AssetCheckAutomationList} from './AssetCheckAutomationList'; import { ASSET_CHECK_DETAILS_QUERY, AgentUpgradeRequired, @@ -52,6 +53,7 @@ export const AssetChecks = ({ const queryResult = useQuery(ASSET_CHECKS_QUERY, { variables: {assetKey}, }); + const {data} = queryResult; useQueryRefreshAtInterval(queryResult, FIFTEEN_SECONDS); @@ -101,6 +103,8 @@ export const AssetChecks = ({ return checks.find((check) => check.name === selectedCheckName) ?? checks[0]; }, [selectedCheckName, checks]); + const isSelectedCheckAutomated = !!selectedCheck?.automationCondition; + const {paginationProps, executions, executionsLoading} = useHistoricalCheckExecutions( selectedCheck ? {assetKey, checkName: selectedCheck.name} : null, ); @@ -189,6 +193,7 @@ export const AssetChecks = ({ $selected={selectedCheck === check} onClick={() => { setSelectedCheckName(check.name); + setActiveTab('overview'); }} > @@ -237,6 +242,7 @@ export const AssetChecks = ({ { setActiveTab(tab); }} @@ -257,6 +263,9 @@ export const AssetChecks = ({ paginationProps={paginationProps} /> ) : null} + {activeTab === 'automation-history' ? ( + + ) : 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 9331e729d1cf3..1be396c818b8c 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 @@ -5,6 +5,15 @@ import { import {ASSET_CHECK_TABLE_FRAGMENT} from './VirtualizedAssetCheckTable'; import {gql} from '../../apollo-client'; +const ASSET_CHECK_KEY_FRAGMENT = gql` + fragment AssetCheckKeyFragment on AssetCheck { + name + assetKey { + path + } + } +`; + export const ASSET_CHECKS_QUERY = gql` query AssetChecksQuery($assetKey: AssetKeyInput!) { assetNodeOrError(assetKey: $assetKey) { @@ -18,6 +27,7 @@ export const ASSET_CHECKS_QUERY = gql` } ... on AssetChecks { checks { + ...AssetCheckKeyFragment ...ExecuteChecksButtonCheckFragment ...AssetCheckTableFragment } @@ -26,7 +36,9 @@ export const ASSET_CHECKS_QUERY = gql` } } } + ${EXECUTE_CHECKS_BUTTON_ASSET_NODE_FRAGMENT} ${EXECUTE_CHECKS_BUTTON_CHECK_FRAGMENT} ${ASSET_CHECK_TABLE_FRAGMENT} + ${ASSET_CHECK_KEY_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 bd5380d6d1933..d7264c45d3cdd 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,17 +1,31 @@ -import {Tab, Tabs} from '@dagster-io/ui-components'; +import {Tab, Tabs, Tooltip} from '@dagster-io/ui-components'; -export type AssetChecksTabType = 'overview' | 'execution-history'; +export type AssetChecksTabType = 'overview' | 'execution-history' | 'automation-history'; interface Props { activeTab: AssetChecksTabType; + enableAutomationHistory: boolean; onChange: (tabId: AssetChecksTabType) => void; } -export const AssetChecksTabs = ({activeTab, onChange}: Props) => { +export const AssetChecksTabs = ({activeTab, enableAutomationHistory, onChange}: Props) => { return ( + + Automation history + + } + disabled={!enableAutomationHistory} + /> ); }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/VirtualizedAssetCheckTable.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/VirtualizedAssetCheckTable.tsx index a08f2801d8b89..593dde9e4284b 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/VirtualizedAssetCheckTable.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/VirtualizedAssetCheckTable.tsx @@ -151,6 +151,10 @@ export const ASSET_CHECK_TABLE_FRAGMENT = gql` name description canExecuteIndividually + automationCondition { + label + expandedLabel + } ...ExecuteChecksButtonCheckFragment executionForLatestMaterialization { ...AssetCheckExecutionFragment diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckAutomationList.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckAutomationList.types.ts new file mode 100644 index 0000000000000..9c97126f8d517 --- /dev/null +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/asset-checks/types/AssetCheckAutomationList.types.ts @@ -0,0 +1,431 @@ +// Generated GraphQL types, do not edit manually. + +import * as Types from '../../../graphql/types'; + +export type AssetCheckAutomationListQueryVariables = Types.Exact<{ + assetCheckKey: Types.AssetCheckHandleInput; + limit: Types.Scalars['Int']['input']; + cursor?: Types.InputMaybe; +}>; + +export type AssetCheckAutomationListQuery = { + __typename: 'Query'; + assetConditionEvaluationRecordsOrError: + | { + __typename: 'AssetConditionEvaluationRecords'; + records: Array<{ + __typename: 'AssetConditionEvaluationRecord'; + id: string; + evaluationId: string; + numRequested: number; + runIds: Array; + timestamp: number; + startTimestamp: number | null; + endTimestamp: number | null; + isLegacy: boolean; + assetKey: {__typename: 'AssetKey'; path: Array} | null; + evaluation: { + __typename: 'AssetConditionEvaluation'; + rootUniqueId: string; + evaluationNodes: Array< + | { + __typename: 'PartitionedAssetConditionEvaluationNode'; + description: string; + startTimestamp: number | null; + endTimestamp: number | null; + numTrue: number; + uniqueId: string; + childUniqueIds: Array; + numCandidates: number | null; + } + | { + __typename: 'SpecificPartitionAssetConditionEvaluationNode'; + description: string; + status: Types.AssetConditionEvaluationStatus; + uniqueId: string; + childUniqueIds: Array; + 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: '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; + } + >; + } + | { + __typename: 'UnpartitionedAssetConditionEvaluationNode'; + description: string; + startTimestamp: number | null; + endTimestamp: number | null; + status: Types.AssetConditionEvaluationStatus; + uniqueId: string; + childUniqueIds: Array; + 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: '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; + } + >; + } + >; + }; + evaluationNodes: Array<{ + __typename: 'AutomationConditionEvaluationNode'; + uniqueId: string; + expandedLabel: Array; + userLabel: string | null; + startTimestamp: number | null; + endTimestamp: number | null; + numCandidates: number | null; + numTrue: number; + isPartitioned: boolean; + childUniqueIds: Array; + }>; + }>; + } + | {__typename: 'AutoMaterializeAssetEvaluationNeedsMigrationError'} + | null; +}; + +export const AssetCheckAutomationListQueryVersion = 'b6dffe3883c3c008672d9366595c876d95e0b1672f891695550ab513c93fa3a8'; 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 0a0802d4b3760..e7d0eff4b81a9 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 @@ -2,6 +2,12 @@ import * as Types from '../../../graphql/types'; +export type AssetCheckKeyFragment = { + __typename: 'AssetCheck'; + name: string; + assetKey: {__typename: 'AssetKey'; path: Array}; +}; + export type AssetChecksQueryVariables = Types.Exact<{ assetKey: Types.AssetKeyInput; }>; @@ -25,6 +31,12 @@ export type AssetChecksQuery = { canExecuteIndividually: Types.AssetCheckCanExecuteIndividually; jobNames: Array; description: string | null; + assetKey: {__typename: 'AssetKey'; path: Array}; + automationCondition: { + __typename: 'AutomationCondition'; + label: string | null; + expandedLabel: Array; + } | null; executionForLatestMaterialization: { __typename: 'AssetCheckExecution'; id: string; @@ -238,4 +250,4 @@ export type AssetChecksQuery = { | {__typename: 'AssetNotFoundError'}; }; -export const AssetChecksQueryVersion = '2487c52958999bb33e5daa6c0caa0eea46ab200cb7d5d2294a8290e8e1ad3025'; +export const AssetChecksQueryVersion = '67252db2bc1bfc878d1568008f7e0698a4515d15b4b68c8e88bd1edef4c1f60f'; 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 b5cd73766262a..96fe6efd4673c 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 @@ -8,6 +8,11 @@ export type AssetCheckTableFragment = { description: string | null; canExecuteIndividually: Types.AssetCheckCanExecuteIndividually; jobNames: Array; + automationCondition: { + __typename: 'AutomationCondition'; + label: string | null; + expandedLabel: Array; + } | null; executionForLatestMaterialization: { __typename: 'AssetCheckExecution'; id: string;