diff --git a/js_modules/dagster-ui/packages/ui-components/src/components/Icon.tsx b/js_modules/dagster-ui/packages/ui-components/src/components/Icon.tsx
index e18eebdb39c08..9bce5a4c8547f 100644
--- a/js_modules/dagster-ui/packages/ui-components/src/components/Icon.tsx
+++ b/js_modules/dagster-ui/packages/ui-components/src/components/Icon.tsx
@@ -106,6 +106,7 @@ import menu_book from '../icon-svgs/menu_book.svg';
import more_horiz from '../icon-svgs/more_horiz.svg';
import ms_teams from '../icon-svgs/ms_teams.svg';
import multi_asset from '../icon-svgs/multi_asset.svg';
+import new_in_branch from '../icon-svgs/new_in_branch.svg';
import nightlight from '../icon-svgs/nightlight.svg';
import no_access from '../icon-svgs/no_access.svg';
import observation from '../icon-svgs/observation.svg';
@@ -310,6 +311,7 @@ export const Icons = {
menu,
menu_book,
more_horiz,
+ new_in_branch,
nightlight,
no_access,
people,
diff --git a/js_modules/dagster-ui/packages/ui-components/src/icon-svgs/new_in_branch.svg b/js_modules/dagster-ui/packages/ui-components/src/icon-svgs/new_in_branch.svg
new file mode 100644
index 0000000000000..e50f8bf288075
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-components/src/icon-svgs/new_in_branch.svg
@@ -0,0 +1,10 @@
+
diff --git a/js_modules/dagster-ui/packages/ui-core/src/app/Flags.tsx b/js_modules/dagster-ui/packages/ui-core/src/app/Flags.tsx
index c0002a922d86a..2fc2910941675 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/app/Flags.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/app/Flags.tsx
@@ -13,6 +13,7 @@ export const FeatureFlag = {
flagDisableAutoLoadDefaults: 'flagDisableAutoLoadDefaults' as const,
flagUseNewAutomationPage: 'flagUseNewAutomationPage' as const,
flagUseNewOverviewPage: 'flagUseNewOverviewPage' as const,
+ flagExperimentalBranchDiff: 'flagExperimentalBranchDiff' as const,
};
export type FeatureFlagType = keyof typeof FeatureFlag;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-data/AssetLiveDataProvider.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-data/AssetLiveDataProvider.tsx
index 8f05021cef6b1..5b5ce73222821 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-data/AssetLiveDataProvider.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-data/AssetLiveDataProvider.tsx
@@ -166,6 +166,7 @@ export const ASSET_NODE_LIVE_FRAGMENT = gql`
}
}
}
+ changedReasons
freshnessInfo {
...AssetNodeLiveFreshnessInfo
}
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-data/types/AssetLiveDataProvider.types.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-data/types/AssetLiveDataProvider.types.ts
index 574b4e11c949a..841e2f5661593 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-data/types/AssetLiveDataProvider.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-data/types/AssetLiveDataProvider.types.ts
@@ -27,6 +27,7 @@ export type AssetNodeLiveFragment = {
__typename: 'AssetNode';
id: string;
opNames: Array;
+ changedReasons: Array;
staleStatus: Types.StaleStatus | null;
repository: {__typename: 'Repository'; id: string};
assetKey: {__typename: 'AssetKey'; path: Array};
@@ -119,6 +120,7 @@ export type AssetGraphLiveQuery = {
__typename: 'AssetNode';
id: string;
opNames: Array;
+ changedReasons: Array;
staleStatus: Types.StaleStatus | null;
repository: {__typename: 'Repository'; id: string};
assetKey: {__typename: 'AssetKey'; path: Array};
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx
index 608d6f5b1650d..984716e2f6cfb 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx
@@ -15,7 +15,8 @@ import {AssetNodeFragment} from './types/AssetNode.types';
import {withMiddleTruncation} from '../app/Util';
import {useAssetLiveData} from '../asset-data/AssetLiveDataProvider';
import {PartitionCountTags} from '../assets/AssetNodePartitionCounts';
-import {StaleReasonsTags} from '../assets/Stale';
+import {ChangedReasonsTag, MinimalNodeChangedDot} from '../assets/ChangedReasons';
+import {MinimalNodeStaleDot, StaleReasonsTag, isAssetStale} from '../assets/Stale';
import {AssetChecksStatusSummary} from '../assets/asset-checks/AssetChecksStatusSummary';
import {assetDetailsPathForKey} from '../assets/assetDetailsPathForKey';
import {AssetComputeKindTag} from '../graph/OpTags';
@@ -34,7 +35,13 @@ export const AssetNode = React.memo(({definition, selected}: Props) => {
return (
-
+
+
+
+
@@ -63,7 +70,6 @@ export const AssetNode = React.memo(({definition, selected}: Props) => {
{definition.isPartitioned && !definition.isSource && (
)}
-
@@ -77,17 +83,6 @@ export const AssetNode = React.memo(({definition, selected}: Props) => {
);
}, isEqual);
-interface AssetTopTagsProps {
- definition: AssetNodeFragment;
- liveData?: LiveDataForNode;
-}
-
-const AssetTopTags = ({definition, liveData}: AssetTopTagsProps) => (
-
-
-
-);
-
const AssetNodeRowBox = styled(Box)`
white-space: nowrap;
line-height: 12px;
@@ -181,6 +176,9 @@ export const AssetNodeMinimal = ({
const {border, background} = buildAssetNodeStatusContent({assetKey, definition, liveData});
const displayName = assetKey.path[assetKey.path.length - 1]!;
+ const isChanged = liveData?.changedReasons?.length;
+ const isStale = isAssetStale(liveData);
+
return (
@@ -196,6 +194,15 @@ export const AssetNodeMinimal = ({
$background={background}
$border={border}
>
+ {isChanged ? (
+
+ ) : null}
+ {isStale ? (
+
+ ) : null}
@@ -219,6 +226,7 @@ export const ASSET_NODE_FRAGMENT = gql`
graphName
hasMaterializePermission
jobNames
+ changedReasons
opNames
opVersion
description
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/Utils.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/Utils.tsx
index 03b3191d33a90..a8c8834dd7c70 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/Utils.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/Utils.tsx
@@ -14,7 +14,7 @@ import {
AssetNodeLiveMaterializationFragment,
AssetNodeLiveObservationFragment,
} from '../asset-data/types/AssetLiveDataProvider.types';
-import {RunStatus, StaleStatus} from '../graphql/types';
+import {ChangeReason, RunStatus, StaleStatus} from '../graphql/types';
/**
* IMPORTANT: This file is used by the WebWorker so make sure we don't indirectly import React or anything that relies on window/document
@@ -154,6 +154,7 @@ export interface LiveDataForNode {
staleStatus: StaleStatus | null;
staleCauses: AssetGraphLiveQuery['assetNodes'][0]['staleCauses'];
assetChecks: AssetCheckLiveFragment[];
+ changedReasons?: Array;
partitionStats: {
numMaterialized: number;
numMaterializing: number;
@@ -165,6 +166,7 @@ export interface LiveDataForNode {
export const MISSING_LIVE_DATA: LiveDataForNode = {
unstartedRunIds: [],
+ changedReasons: [],
inProgressRunIds: [],
runWhichFailedToMaterialize: null,
freshnessInfo: null,
@@ -217,6 +219,7 @@ export const buildLiveDataForNode = (
return {
lastMaterialization,
+ changedReasons: assetNode.changedReasons,
lastMaterializationRunStatus:
latestRunForAsset && lastMaterialization?.runId === latestRunForAsset?.id
? latestRunForAsset.status
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts
index 1357d5c377c55..796b086c404ea 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__fixtures__/AssetNode.fixtures.ts
@@ -1,6 +1,7 @@
import {
AssetCheckExecutionResolvedStatus,
AssetCheckSeverity,
+ ChangeReason,
RunStatus,
StaleCause,
StaleCauseCategory,
@@ -24,7 +25,7 @@ export const MockStaleReasonData: StaleCause = {
__typename: 'AssetKey',
},
partitionKey: null,
- reason: 'updated data version',
+ reason: 'has a new data version',
category: StaleCauseCategory.DATA,
dependency: {
path: ['asset0'],
@@ -40,7 +41,7 @@ export const MockStaleReasonCode: StaleCause = {
__typename: 'AssetKey',
},
partitionKey: null,
- reason: 'code version changed',
+ reason: 'has a new code version',
category: StaleCauseCategory.CODE,
dependency: {
path: ['asset1'],
@@ -64,6 +65,12 @@ export const AssetNodeFragmentBasic: AssetNodeFragment = buildAssetNode({
jobNames: ['job1'],
opNames: ['asset1'],
opVersion: '1',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
});
export const AssetNodeFragmentSource = buildAssetNode({
@@ -96,6 +103,12 @@ export const AssetNodeFragmentPartitioned: AssetNodeFragment = buildAssetNode({
export const LiveDataForNodeRunStartedNotMaterializing: LiveDataForNode = {
stepKey: 'asset2',
unstartedRunIds: ['ABCDEF'],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
inProgressRunIds: [],
lastMaterialization: null,
lastMaterializationRunStatus: null,
@@ -112,6 +125,12 @@ export const LiveDataForNodeRunStartedNotMaterializing: LiveDataForNode = {
export const LiveDataForNodeRunStartedMaterializing: LiveDataForNode = {
stepKey: 'asset3',
unstartedRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
inProgressRunIds: ['ABCDEF'],
lastMaterialization: null,
lastMaterializationRunStatus: null,
@@ -128,6 +147,12 @@ export const LiveDataForNodeRunStartedMaterializing: LiveDataForNode = {
export const LiveDataForNodeRunFailed: LiveDataForNode = {
stepKey: 'asset4',
unstartedRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
inProgressRunIds: [],
lastMaterialization: null,
lastMaterializationRunStatus: null,
@@ -150,6 +175,12 @@ export const LiveDataForNodeNeverMaterialized: LiveDataForNode = {
stepKey: 'asset5',
unstartedRunIds: [],
inProgressRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastMaterialization: null,
lastMaterializationRunStatus: null,
lastObservation: null,
@@ -165,6 +196,12 @@ export const LiveDataForNodeNeverMaterialized: LiveDataForNode = {
export const LiveDataForNodeMaterialized: LiveDataForNode = {
stepKey: 'asset6',
unstartedRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
runId: 'ABCDEF',
@@ -183,6 +220,12 @@ export const LiveDataForNodeMaterialized: LiveDataForNode = {
export const LiveDataForNodeMaterializedWithChecks: LiveDataForNode = {
stepKey: 'asset7',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -260,6 +303,12 @@ export const LiveDataForNodeMaterializedWithChecksOk: LiveDataForNode = {
export const LiveDataForNodeMaterializedAndStale: LiveDataForNode = {
stepKey: 'asset8',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -279,6 +328,12 @@ export const LiveDataForNodeMaterializedAndStale: LiveDataForNode = {
export const LiveDataForNodeMaterializedAndStaleAndOverdue: LiveDataForNode = {
stepKey: 'asset9',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -301,6 +356,12 @@ export const LiveDataForNodeMaterializedAndStaleAndOverdue: LiveDataForNode = {
export const LiveDataForNodeMaterializedAndStaleAndFresh: LiveDataForNode = {
stepKey: 'asset10',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -323,6 +384,12 @@ export const LiveDataForNodeMaterializedAndStaleAndFresh: LiveDataForNode = {
export const LiveDataForNodeMaterializedAndFresh: LiveDataForNode = {
stepKey: 'asset11',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -345,6 +412,12 @@ export const LiveDataForNodeMaterializedAndFresh: LiveDataForNode = {
export const LiveDataForNodeMaterializedAndOverdue: LiveDataForNode = {
stepKey: 'asset12',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: buildMaterializationEvent({
@@ -370,6 +443,12 @@ export const LiveDataForNodeFailedAndOverdue: LiveDataForNode = {
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterializationRunStatus: null,
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastObservation: null,
lastMaterialization: null,
runWhichFailedToMaterialize: buildRun({
@@ -391,6 +470,12 @@ export const LiveDataForNodeSourceNeverObserved: LiveDataForNode = {
stepKey: 'source_asset2',
unstartedRunIds: [],
inProgressRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastMaterialization: null,
lastMaterializationRunStatus: null,
lastObservation: null,
@@ -408,6 +493,12 @@ export const LiveDataForNodeSourceObservationRunning: LiveDataForNode = {
stepKey: 'source_asset3',
unstartedRunIds: [],
inProgressRunIds: ['ABCDEF'],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastMaterialization: null,
lastMaterializationRunStatus: null,
lastObservation: null,
@@ -433,6 +524,12 @@ export const LiveDataForNodeSourceObservedUpToDate: LiveDataForNode = {
runWhichFailedToMaterialize: null,
staleStatus: StaleStatus.FRESH,
staleCauses: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
assetChecks: [],
freshnessInfo: null,
opNames: [],
@@ -451,6 +548,12 @@ export const LiveDataForNodePartitionedSomeMissing: LiveDataForNode = {
lastMaterializationRunStatus: null,
lastObservation: null,
runWhichFailedToMaterialize: null,
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
staleStatus: StaleStatus.FRESH,
staleCauses: [],
assetChecks: [],
@@ -473,6 +576,12 @@ export const LiveDataForNodePartitionedSomeFailed: LiveDataForNode = {
runId: 'ABCDEF',
timestamp: TIMESTAMP,
},
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastMaterializationRunStatus: null,
lastObservation: null,
runWhichFailedToMaterialize: null,
@@ -501,6 +610,12 @@ export const LiveDataForNodePartitionedNoneMissing: LiveDataForNode = {
lastMaterializationRunStatus: null,
lastObservation: null,
runWhichFailedToMaterialize: null,
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
staleStatus: StaleStatus.FRESH,
staleCauses: [],
assetChecks: [],
@@ -519,6 +634,12 @@ export const LiveDataForNodePartitionedNeverMaterialized: LiveDataForNode = {
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: null,
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
lastMaterializationRunStatus: null,
lastObservation: null,
runWhichFailedToMaterialize: null,
@@ -547,6 +668,12 @@ export const LiveDataForNodePartitionedMaterializing: LiveDataForNode = {
staleCauses: [],
assetChecks: [],
freshnessInfo: null,
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
partitionStats: {
numMaterialized: 0,
numMaterializing: 5,
@@ -571,6 +698,12 @@ export const LiveDataForNodePartitionedStale: LiveDataForNode = {
staleStatus: StaleStatus.STALE,
staleCauses: [MockStaleReasonData],
assetChecks: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
freshnessInfo: null,
partitionStats: {
numMaterialized: 1500,
@@ -584,6 +717,12 @@ export const LiveDataForNodePartitionedStale: LiveDataForNode = {
export const LiveDataForNodePartitionedOverdue: LiveDataForNode = {
stepKey: 'asset23',
unstartedRunIds: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
inProgressRunIds: [],
lastMaterialization: {
__typename: 'MaterializationEvent',
@@ -635,10 +774,22 @@ export const LiveDataForNodePartitionedFresh: LiveDataForNode = {
numFailed: 0,
},
opNames: [],
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
};
export const LiveDataForNodePartitionedLatestRunFailed: LiveDataForNode = {
stepKey: 'asset25',
+ changedReasons: [
+ ChangeReason.NEW,
+ ChangeReason.CODE_VERSION,
+ ChangeReason.INPUTS,
+ ChangeReason.PARTITIONS_DEFINITION,
+ ],
unstartedRunIds: [],
inProgressRunIds: [],
lastMaterialization: null,
@@ -719,14 +870,14 @@ export const AssetNodeScenariosBase = [
title: 'Materialized and Stale',
liveData: LiveDataForNodeMaterializedAndStale,
definition: AssetNodeFragmentBasic,
- expectedText: ['Upstream code version', 'Feb'],
+ expectedText: ['Outdated', 'Feb'],
},
{
title: 'Materialized and Stale and Overdue',
liveData: LiveDataForNodeMaterializedAndStaleAndOverdue,
definition: AssetNodeFragmentBasic,
- expectedText: ['Upstream code version', 'Overdue', 'Feb'],
+ expectedText: ['Outdated', 'Overdue', 'Feb'],
},
{
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx
index 778ea8463786d..b7857dc3d6f23 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/__stories__/AssetNode.stories.tsx
@@ -22,7 +22,7 @@ export const LiveStates = () => {
...scenario.definition,
assetKey: {
...scenario.definition.assetKey,
- path: [],
+ path: [] as string[],
},
};
definitionCopy.assetKey.path = scenario.liveData
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/AssetNode.types.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/AssetNode.types.ts
index d9d9e76bdf19f..9d1d4b5b920ea 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/AssetNode.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/AssetNode.types.ts
@@ -8,6 +8,7 @@ export type AssetNodeFragment = {
graphName: string | null;
hasMaterializePermission: boolean;
jobNames: Array;
+ changedReasons: Array;
opNames: Array;
opVersion: string | null;
description: string | null;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/useAssetGraphData.types.ts b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/useAssetGraphData.types.ts
index ee4cc66ed3b78..9b05aee106d61 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/useAssetGraphData.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/types/useAssetGraphData.types.ts
@@ -17,6 +17,7 @@ export type AssetGraphQuery = {
hasMaterializePermission: boolean;
graphName: string | null;
jobNames: Array;
+ changedReasons: Array;
opNames: Array;
opVersion: string | null;
description: string | null;
@@ -44,6 +45,7 @@ export type AssetNodeForGraphQueryFragment = {
hasMaterializePermission: boolean;
graphName: string | null;
jobNames: Array;
+ changedReasons: Array;
opNames: Array;
opVersion: string | null;
description: string | null;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEvents.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEvents.tsx
index 1974d6038f12a..a4c243a1e8836 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEvents.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetEvents.tsx
@@ -207,6 +207,7 @@ export const AssetEvents = ({
assetKey={assetKey}
stepKey={assetNode ? stepKeyForAsset(assetNode) : undefined}
latestRunForPartition={null}
+ changedReasons={assetNode?.changedReasons}
/>
) : (
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetPartitionDetail.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetPartitionDetail.tsx
index 0c4a4b5c45567..af8de7ff2d44e 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetPartitionDetail.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetPartitionDetail.tsx
@@ -19,8 +19,9 @@ import {AllIndividualEventsButton} from './AllIndividualEventsButton';
import {AssetEventMetadataEntriesTable} from './AssetEventMetadataEntriesTable';
import {AssetEventSystemTags} from './AssetEventSystemTags';
import {AssetMaterializationUpstreamData} from './AssetMaterializationUpstreamData';
+import {ChangedReasonsTag} from './ChangedReasons';
import {FailedRunSinceMaterializationBanner} from './FailedRunSinceMaterializationBanner';
-import {StaleReasonsTags} from './Stale';
+import {StaleReasonsTag} from './Stale';
import {AssetEventGroup} from './groupByPartition';
import {AssetKey} from './types';
import {
@@ -181,6 +182,7 @@ export const AssetPartitionDetail = ({
latestRunForPartition,
staleCauses,
staleStatus,
+ changedReasons,
}: {
assetKey: AssetKey;
group: AssetEventGroup;
@@ -191,6 +193,7 @@ export const AssetPartitionDetail = ({
stepKey?: string;
staleCauses?: LiveDataForNode['staleCauses'];
staleStatus?: LiveDataForNode['staleStatus'];
+ changedReasons?: LiveDataForNode['changedReasons'];
}) => {
const {latest, partition, all} = group;
@@ -247,13 +250,12 @@ export const AssetPartitionDetail = ({
) : undefined}
{hasStaleLoadingState ? (
- ) : staleCauses && staleStatus ? (
-
- ) : undefined}
+ ) : (
+
+
+
+
+ )}
) : (
No partition selected
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx
index e1c064d0015a2..b90ab13b1fb8a 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx
@@ -21,6 +21,7 @@ import {AssetAutomaterializePolicyPage} from './AutoMaterializePolicyPage/AssetA
import {AssetAutomaterializePolicyPageOld} from './AutoMaterializePolicyPageOld/AssetAutomaterializePolicyPage';
import {useAutoMaterializeSensorFlag} from './AutoMaterializeSensorFlag';
import {AutomaterializeDaemonStatusTag} from './AutomaterializeDaemonStatusTag';
+import {ChangedReasonsTag} from './ChangedReasons';
import {LaunchAssetExecutionButton} from './LaunchAssetExecutionButton';
import {LaunchAssetObservationButton} from './LaunchAssetObservationButton';
import {OverdueTag} from './OverdueTag';
@@ -45,7 +46,7 @@ import {
tokenForAssetKey,
} from '../asset-graph/Utils';
import {useAssetGraphData} from '../asset-graph/useAssetGraphData';
-import {StaleReasonsTags} from '../assets/Stale';
+import {StaleReasonsTag} from '../assets/Stale';
import {AssetComputeKindTag} from '../graph/OpTags';
import {useQueryPersistedState} from '../hooks/useQueryPersistedState';
import {RepositoryLink} from '../nav/RepositoryLink';
@@ -526,12 +527,17 @@ const AssetViewPageHeaderTags = ({
return (
<>
{definition ? (
-
+ <>
+
+
+ >
) : null}
{definition?.isSource ? (
Source Asset
@@ -567,17 +573,20 @@ const AssetViewPageHeaderTags = ({
{definition && definition.freshnessPolicy && (
)}
- {definition && (
-
- )}
- {definition && (
-
- )}
+ {definition ? (
+ <>
+
+
+
+ >
+ ) : null}
>
);
};
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/ChangedReasons.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/ChangedReasons.tsx
new file mode 100644
index 0000000000000..35f1584a66b94
--- /dev/null
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/ChangedReasons.tsx
@@ -0,0 +1,139 @@
+import {
+ BaseTag,
+ Box,
+ Colors,
+ Icon,
+ Popover,
+ Subtitle2,
+ Tag,
+ ifPlural,
+} from '@dagster-io/ui-components';
+import styled from 'styled-components';
+
+import {FeatureFlag, featureEnabled} from '../app/Flags';
+import {displayNameForAssetKey} from '../asset-graph/Utils';
+import {AssetKeyInput, ChangeReason} from '../graphql/types';
+import {numberFormatter} from '../ui/formatters';
+
+export const ChangedReasonsTag = ({
+ changedReasons,
+ assetKey,
+}: {
+ changedReasons?: ChangeReason[];
+ assetKey: AssetKeyInput;
+}) => {
+ const flagExperimentalBranchDiff = featureEnabled(FeatureFlag.flagExperimentalBranchDiff);
+ if (!changedReasons?.length || !flagExperimentalBranchDiff) {
+ return null;
+ }
+ return (
+
+ }
+ />
+
+ );
+};
+
+export const ChangedReasonsPopover = ({
+ changedReasons,
+ assetKey,
+ children,
+}: {
+ changedReasons: ChangeReason[];
+ assetKey: AssetKeyInput;
+ children: React.ReactNode;
+}) => {
+ const flagExperimentalBranchDiff = featureEnabled(FeatureFlag.flagExperimentalBranchDiff);
+ if (!flagExperimentalBranchDiff) {
+ return null;
+ }
+ const modifiedChanges = changedReasons.filter((reason) => reason !== ChangeReason.NEW);
+ function getDescription(change: ChangeReason) {
+ switch (change) {
+ case ChangeReason.NEW:
+ return '';
+ case ChangeReason.CODE_VERSION:
+ return 'has a modified code version';
+ case ChangeReason.INPUTS:
+ return 'has modified dependencies';
+ case ChangeReason.PARTITIONS_DEFINITION:
+ return 'has a modified partition definition';
+ }
+ }
+ return (
+
+
+
+ {numberFormatter.format(modifiedChanges.length)}{' '}
+ {ifPlural(modifiedChanges.length, 'change', 'changes')} in this branch
+
+
+ {modifiedChanges.map((change) => {
+ return (
+
+ {displayNameForAssetKey(assetKey)}
+ {getDescription(change)}
+
+ );
+ })}
+
+ }
+ interactionKind="hover"
+ className="chunk-popover-target"
+ >
+ {children}
+
+ );
+};
+
+export const MinimalNodeChangedDot = ({
+ changedReasons,
+ assetKey,
+}: {
+ changedReasons: ChangeReason[];
+ assetKey: AssetKeyInput;
+}) => {
+ const flagExperimentalBranchDiff = featureEnabled(FeatureFlag.flagExperimentalBranchDiff);
+ if (!flagExperimentalBranchDiff) {
+ return null;
+ }
+ return (
+
+
+
+ );
+};
+
+const MinimalNodeChangedDotContainer = styled.div`
+ position: absolute;
+ right: 6px;
+ top: 6px;
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ background-color: ${Colors.backgroundCyan()};
+ &:after {
+ display: block;
+ position: absolute;
+ content: ' ';
+ left: 5px;
+ top: 5px;
+ height: 10px;
+ width: 10px;
+ border-radius: 50%;
+ background-color: ${Colors.accentCyan()};
+ }
+`;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/LastMaterializationMetadata.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/LastMaterializationMetadata.tsx
index 9ca98e9f1488c..954017e59a5e1 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/LastMaterializationMetadata.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/LastMaterializationMetadata.tsx
@@ -3,7 +3,8 @@ import {Link} from 'react-router-dom';
import styled from 'styled-components';
import {AssetLineageElements} from './AssetLineageElements';
-import {StaleReasonsTags} from './Stale';
+import {ChangedReasonsTag} from './ChangedReasons';
+import {StaleReasonsTag} from './Stale';
import {isRunlessEvent} from './isRunlessEvent';
import {
AssetMaterializationFragment,
@@ -118,7 +119,13 @@ export const LatestMaterializationMetadata = ({
{liveData && (
-
+ <>
+
+
+ >
)}
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/Stale.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/Stale.tsx
index c99b68f218388..5ffdaa8f8d3c2 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/Stale.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/Stale.tsx
@@ -4,22 +4,30 @@ import {
Box,
ButtonLink,
Caption,
- CaptionMono,
+ CaptionSubtitle,
Colors,
Icon,
Popover,
+ Subtitle2,
+ Tag,
+ ifPlural,
} from '@dagster-io/ui-components';
import groupBy from 'lodash/groupBy';
import isEqual from 'lodash/isEqual';
+import React from 'react';
import {Link} from 'react-router-dom';
+import styled from 'styled-components';
import {assetDetailsPathForKey} from './assetDetailsPathForKey';
import {LiveDataForNode, displayNameForAssetKey} from '../asset-graph/Utils';
-import {AssetNodeKeyFragment} from '../asset-graph/types/AssetNode.types';
import {AssetKeyInput, StaleCauseCategory, StaleStatus} from '../graphql/types';
+import {numberFormatter} from '../ui/formatters';
-type StaleDataForNode = Pick;
-
+type StaleDataForNode = {
+ staleCauses?: LiveDataForNode['staleCauses'];
+ staleStatus?: LiveDataForNode['staleStatus'];
+ changedReasons?: LiveDataForNode['changedReasons'];
+};
export const isAssetMissing = (liveData?: Pick) =>
liveData && liveData.staleStatus === StaleStatus.MISSING;
@@ -39,6 +47,30 @@ const LABELS = {
},
};
+function getCollapsedHeaderLabel(isSelf: boolean, category: StaleCauseCategory, count: number) {
+ const upstreamString = isSelf ? ' ' : ' upstream ';
+ switch (category) {
+ case StaleCauseCategory.CODE:
+ if (count === 1) {
+ return `1${upstreamString}code version change`;
+ } else {
+ return `${count}${upstreamString}code version changes`;
+ }
+ case StaleCauseCategory.DATA:
+ if (count === 1) {
+ return `1${upstreamString}data version change`;
+ } else {
+ return `${count}${upstreamString}data version changes`;
+ }
+ case StaleCauseCategory.DEPENDENCIES:
+ if (count === 1) {
+ return `1${upstreamString}dependency change`;
+ } else {
+ return `${count}${upstreamString}dependency changes`;
+ }
+ }
+}
+
export const StaleReasonsLabel = ({
liveData,
include,
@@ -48,7 +80,7 @@ export const StaleReasonsLabel = ({
include: 'all' | 'upstream' | 'self';
liveData?: StaleDataForNode;
}) => {
- if (!isAssetStale(liveData) || !liveData?.staleCauses.length) {
+ if (!isAssetStale(liveData) || !liveData?.staleCauses?.length) {
return null;
}
@@ -56,7 +88,9 @@ export const StaleReasonsLabel = ({
}
+ content={
+
+ }
interactionKind="hover"
className="chunk-popover-target"
>
@@ -66,49 +100,70 @@ export const StaleReasonsLabel = ({
);
};
-export const StaleReasonsTags = ({
- liveData,
- include,
+// Includes the cha
+export const StaleReasonsTag = ({
assetKey,
+ liveData,
+ include = 'all',
onClick,
}: {
assetKey: AssetKeyInput;
- include: 'all' | 'upstream' | 'self';
liveData?: StaleDataForNode;
+ include?: 'all' | 'upstream' | 'self';
onClick?: () => void;
}) => {
- if (!isAssetStale(liveData) || !liveData?.staleCauses.length) {
- return null;
+ if (!isAssetStale(liveData) || !liveData?.staleCauses?.length) {
+ return ;
}
+ const label = Outdated ({numberFormatter.format(liveData.staleCauses.length)});
+ return (
+
+
+ }
+ label={
+ onClick ? (
+
+ {label}
+
+ ) : (
+ label
+ )
+ }
+ />
+
+
+ );
+};
+export const StaleCausesPopover = ({
+ liveData,
+ assetKey,
+ include,
+ children,
+}: {
+ assetKey: AssetKeyInput;
+ liveData?: StaleDataForNode;
+ include?: 'all' | 'upstream' | 'self';
+ children: React.ReactNode;
+}) => {
return (
- <>
- {Object.entries(groupedCauses(assetKey, include, liveData)).map(([label, causes]) => (
- }
- position="top"
- interactionKind="hover"
- className="chunk-popover-target"
- >
- }
- label={
- onClick ? (
-
- {label}
-
- ) : (
- label
- )
- }
- />
-
- ))}
- >
+
+ }
+ position="top-left"
+ interactionKind="hover"
+ className="chunk-popover-target"
+ >
+ {children}
+
);
};
@@ -127,33 +182,69 @@ function groupedCauses(
return groupBy(all, (cause) => cause.label);
}
-const StaleCausesPopoverSummary = ({causes}: {causes: LiveDataForNode['staleCauses']}) => (
-
-
- Changes since last materialization:
-
- e.stopPropagation()}>
- {causes.map((cause, idx) => (
- 0 ? 'top' : null} padding={{vertical: 8, horizontal: 12}}>
-
- {displayNameForAssetKey(cause.key)}
-
-
-
- ))}
-
-
-);
-
-const StaleReason = ({
- reason,
- dependency,
+const StaleCausesPopoverSummary = ({
+ assetKey,
+ liveData,
+ include = 'all',
}: {
- reason: string;
- dependency: AssetNodeKeyFragment | null;
+ assetKey: AssetKeyInput;
+ liveData?: StaleDataForNode;
+ include?: 'all' | 'upstream' | 'self';
}) => {
+ const grouped = groupedCauses(assetKey, include, liveData);
+ const totalCauses = liveData?.staleCauses?.length;
+
+ if (!totalCauses) {
+ // Should never happen since the parent of this component should check that the asset is stale before rendering the popover
+ return ;
+ }
+ return (
+
+
+
+ {numberFormatter.format(totalCauses)} {ifPlural(totalCauses, 'change', 'changes')} since
+ last materialization
+
+
+ {Object.entries(grouped).map(([label, causes], idx) => {
+ const isSelf = isEqual(assetKey.path, causes[0]!.key.path);
+ return (
+
+
+
+ {getCollapsedHeaderLabel(isSelf, causes[0]!.category, causes.length)}
+
+
+ {causes.map((cause, idx) => (
+
+
+
+ ))}
+
+ );
+ })}
+
+ );
+};
+
+const StaleReason = ({cause}: {cause: NonNullable[0]}) => {
+ const {dependency, reason, key} = cause;
if (!dependency) {
- return {` ${reason}`};
+ return (
+ <>
+
+ {displayNameForAssetKey(key)}
+
+ {` ${reason}`}
+ >
+ );
}
const dependencyName = displayNameForAssetKey(dependency);
@@ -161,16 +252,58 @@ const StaleReason = ({
if (reason.endsWith(`${dependencyPythonName}`)) {
const reasonUpToDep = reason.slice(0, -dependencyPythonName.length);
return (
-
- {` ${reasonUpToDep}`}
- {dependencyName}
-
+ <>
+ {reasonUpToDep}
+
+ {dependencyName}
+
+ >
);
}
return (
-
- {` ${reason} `}({dependencyName})
-
+ <>
+
+ {dependencyName}
+
+ {` ${reason} `}
+ >
+ );
+};
+
+export const MinimalNodeStaleDot = ({
+ liveData,
+ assetKey,
+ include = 'all',
+}: {
+ liveData?: StaleDataForNode;
+ assetKey: AssetKeyInput;
+ include?: 'all' | 'upstream' | 'self';
+}) => {
+ return (
+
+
+
);
};
+
+const MinimalNodeStaleDotElement = styled.div`
+ position: absolute;
+ left: 6px;
+ top: 6px;
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ background-color: ${Colors.backgroundYellow()};
+ &:after {
+ display: block;
+ position: absolute;
+ content: ' ';
+ left: 5px;
+ top: 5px;
+ height: 10px;
+ width: 10px;
+ border-radius: 50%;
+ background-color: ${Colors.accentYellow()};
+ }
+`;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/__fixtures__/AssetTables.fixtures.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/__fixtures__/AssetTables.fixtures.ts
index 88d2a67d9000b..1b234213e3cb6 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/__fixtures__/AssetTables.fixtures.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/__fixtures__/AssetTables.fixtures.ts
@@ -1,61 +1,71 @@
-import {MockedResponse} from '@apollo/client/testing';
-
import {ASSETS_GRAPH_LIVE_QUERY} from '../../asset-data/AssetLiveDataProvider';
-import {AssetGraphLiveQuery} from '../../asset-data/types/AssetLiveDataProvider.types';
+import {
+ AssetGraphLiveQuery,
+ AssetGraphLiveQueryVariables,
+} from '../../asset-data/types/AssetLiveDataProvider.types';
import {MockStaleReasonData} from '../../asset-graph/__fixtures__/AssetNode.fixtures';
import {
+ Asset,
RunStatus,
StaleStatus,
+ buildAsset,
buildAssetChecks,
+ buildAssetConnection,
+ buildAssetFreshnessInfo,
buildAssetKey,
+ buildAssetLatestInfo,
buildAssetNode,
buildFreshnessPolicy,
+ buildMaterializationEvent,
+ buildObservationEvent,
buildPartitionDefinition,
+ buildPartitionStats,
buildRepository,
buildRepositoryLocation,
+ buildRun,
} from '../../graphql/types';
+import {buildQueryMock} from '../../testing/mocking';
import {SINGLE_NON_SDA_ASSET_QUERY} from '../../workspace/VirtualizedAssetRow';
-import {SingleNonSdaAssetQuery} from '../../workspace/types/VirtualizedAssetRow.types';
+import {
+ SingleNonSdaAssetQuery,
+ SingleNonSdaAssetQueryVariables,
+} from '../../workspace/types/VirtualizedAssetRow.types';
import {ASSET_CATALOG_GROUP_TABLE_QUERY, ASSET_CATALOG_TABLE_QUERY} from '../AssetsCatalogTable';
import {
AssetCatalogGroupTableQuery,
+ AssetCatalogGroupTableQueryVariables,
AssetCatalogTableQuery,
+ AssetCatalogTableQueryVariables,
} from '../types/AssetsCatalogTable.types';
-export const AssetCatalogGroupTableMock: MockedResponse = {
- request: {
- query: ASSET_CATALOG_GROUP_TABLE_QUERY,
- },
- result: {
- data: {
- __typename: 'Query',
- assetNodes: [],
- },
+export const AssetCatalogGroupTableMock = buildQueryMock<
+ AssetCatalogGroupTableQuery,
+ AssetCatalogGroupTableQueryVariables
+>({
+ query: ASSET_CATALOG_GROUP_TABLE_QUERY,
+ data: {
+ assetNodes: [],
},
-};
+});
-export const SingleAssetQueryTrafficDashboard: MockedResponse = {
- request: {
- query: SINGLE_NON_SDA_ASSET_QUERY,
- variables: {input: {path: ['dashboards', 'traffic_dashboard']}},
- },
- result: {
- data: {
- __typename: 'Query',
- assetOrError: {
- id: '["dashboards", "traffic_dashboard"]',
- assetMaterializations: [
- {
- timestamp: '1674603883946',
- runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
- __typename: 'MaterializationEvent',
- },
- ],
- __typename: 'Asset',
- },
- },
+export const SingleAssetQueryTrafficDashboard = buildQueryMock<
+ SingleNonSdaAssetQuery,
+ SingleNonSdaAssetQueryVariables
+>({
+ query: SINGLE_NON_SDA_ASSET_QUERY,
+ variables: {input: {path: ['dashboards', 'traffic_dashboard']}},
+ data: {
+ assetOrError: buildAsset({
+ id: '["dashboards", "traffic_dashboard"]',
+ assetMaterializations: [
+ buildMaterializationEvent({
+ timestamp: '1674603883946',
+ runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
+ }),
+ ],
+ }),
},
-};
+});
const repository = buildRepository({
id: 'c22d9677b8089be89b1e014b9de34284962f83a7',
@@ -63,211 +73,177 @@ const repository = buildRepository({
location: buildRepositoryLocation({
id: 'test.py',
name: 'test.py',
- __typename: 'RepositoryLocation',
}),
- __typename: 'Repository',
});
-export const SingleAssetQueryMaterializedWithLatestRun: MockedResponse = {
- request: {
- query: ASSETS_GRAPH_LIVE_QUERY,
- variables: {assetKeys: [{path: ['good_asset']}]},
- },
- result: {
- data: {
- __typename: 'Query',
- assetNodes: [
- {
- id: 'test.py.repo.["good_asset"]',
- opNames: ['good_asset'],
- repository,
- partitionStats: null,
- assetKey: {
- path: ['good_asset'],
- __typename: 'AssetKey',
- },
- assetMaterializations: [
- {
- timestamp: '1674603883946',
- runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
- __typename: 'MaterializationEvent',
- },
- ],
- assetChecksOrError: buildAssetChecks(),
- freshnessInfo: null,
- assetObservations: [
- {
- timestamp: '1674764717707',
- runId: 'ae107ad2-8827-44fb-bc62-a4cdacb78438',
- __typename: 'ObservationEvent',
- },
- ],
- staleStatus: StaleStatus.FRESH,
- staleCauses: [],
- __typename: 'AssetNode',
- },
- ],
- assetsLatestInfo: [
- {
- id: 'test.py.repo.["good_asset"]',
- assetKey: {
- path: ['good_asset'],
- __typename: 'AssetKey',
- },
- unstartedRunIds: [],
- inProgressRunIds: [],
- latestRun: {
- id: 'db44ed48-0dca-4942-803b-5edc439c73eb',
- status: RunStatus.SUCCESS,
- endTime: 1674603891.34749,
- __typename: 'Run',
- },
- __typename: 'AssetLatestInfo',
- },
- ],
- },
+export const SingleAssetQueryMaterializedWithLatestRun = buildQueryMock<
+ AssetGraphLiveQuery,
+ AssetGraphLiveQueryVariables
+>({
+ query: ASSETS_GRAPH_LIVE_QUERY,
+ variables: {assetKeys: [{path: ['good_asset']}]},
+ data: {
+ assetNodes: [
+ buildAssetNode({
+ id: 'test.py.repo.["good_asset"]',
+ opNames: ['good_asset'],
+ repository,
+ partitionStats: null,
+ assetKey: buildAssetKey({
+ path: ['good_asset'],
+ }),
+ assetMaterializations: [
+ buildMaterializationEvent({
+ timestamp: '1674603883946',
+ runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
+ }),
+ ],
+ assetChecksOrError: buildAssetChecks(),
+ freshnessInfo: null,
+ assetObservations: [
+ buildObservationEvent({
+ timestamp: '1674764717707',
+ runId: 'ae107ad2-8827-44fb-bc62-a4cdacb78438',
+ }),
+ ],
+ staleStatus: StaleStatus.FRESH,
+ staleCauses: [],
+ }),
+ ],
+ assetsLatestInfo: [
+ buildAssetLatestInfo({
+ id: 'test.py.repo.["good_asset"]',
+ assetKey: buildAssetKey({
+ path: ['good_asset'],
+ }),
+ unstartedRunIds: [],
+ inProgressRunIds: [],
+ latestRun: buildRun({
+ id: 'db44ed48-0dca-4942-803b-5edc439c73eb',
+ status: RunStatus.SUCCESS,
+ endTime: 1674603891.34749,
+ }),
+ }),
+ ],
},
-};
+});
-export const SingleAssetQueryMaterializedStaleAndLate: MockedResponse = {
- request: {
- query: ASSETS_GRAPH_LIVE_QUERY,
- variables: {assetKeys: [{path: ['late_asset']}]},
- },
- result: {
- data: {
- __typename: 'Query',
- assetNodes: [
- {
- id: 'test.py.repo.["late_asset"]',
- opNames: ['late_asset'],
- repository,
- partitionStats: null,
- assetKey: {
- path: ['late_asset'],
- __typename: 'AssetKey',
- },
- assetChecksOrError: buildAssetChecks(),
- assetMaterializations: [
- {
- timestamp: '1674603891025',
- runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
- __typename: 'MaterializationEvent',
- },
- ],
- freshnessInfo: {
- currentMinutesLate: 21657.2618512,
- __typename: 'AssetFreshnessInfo',
- },
- assetObservations: [],
- staleStatus: StaleStatus.STALE,
- staleCauses: [MockStaleReasonData],
- __typename: 'AssetNode',
+export const SingleAssetQueryMaterializedStaleAndLate = buildQueryMock<
+ AssetGraphLiveQuery,
+ AssetGraphLiveQueryVariables
+>({
+ query: ASSETS_GRAPH_LIVE_QUERY,
+ variables: {assetKeys: [{path: ['late_asset']}]},
+ data: {
+ assetNodes: [
+ buildAssetNode({
+ id: 'test.py.repo.["late_asset"]',
+ opNames: ['late_asset'],
+ repository,
+ partitionStats: null,
+ assetKey: {
+ path: ['late_asset'],
+ __typename: 'AssetKey',
},
- ],
- assetsLatestInfo: [
- {
- id: 'test.py.repo.["late_asset"]',
- assetKey: {
- path: ['late_asset'],
- __typename: 'AssetKey',
- },
- unstartedRunIds: [],
- inProgressRunIds: [],
- latestRun: {
- id: 'db44ed48-0dca-4942-803b-5edc439c73eb',
- status: RunStatus.SUCCESS,
- endTime: 1674603891.34749,
- __typename: 'Run',
- },
- __typename: 'AssetLatestInfo',
- },
- ],
- },
+ assetChecksOrError: buildAssetChecks(),
+ assetMaterializations: [
+ buildMaterializationEvent({
+ timestamp: '1674603891025',
+ runId: 'db44ed48-0dca-4942-803b-5edc439c73eb',
+ }),
+ ],
+ freshnessInfo: buildAssetFreshnessInfo({
+ currentMinutesLate: 21657.2618512,
+ }),
+ assetObservations: [],
+ staleStatus: StaleStatus.STALE,
+ staleCauses: [MockStaleReasonData],
+ }),
+ ],
+ assetsLatestInfo: [
+ buildAssetLatestInfo({
+ id: 'test.py.repo.["late_asset"]',
+ assetKey: buildAssetKey({
+ path: ['late_asset'],
+ }),
+ unstartedRunIds: [],
+ inProgressRunIds: [],
+ latestRun: buildRun({
+ id: 'db44ed48-0dca-4942-803b-5edc439c73eb',
+ status: RunStatus.SUCCESS,
+ endTime: 1674603891.34749,
+ }),
+ }),
+ ],
},
-};
+});
-export const SingleAssetQueryLastRunFailed: MockedResponse = {
- request: {
- query: ASSETS_GRAPH_LIVE_QUERY,
- variables: {assetKeys: [{path: ['run_failing_asset']}]},
- },
- result: {
- data: {
- __typename: 'Query',
- assetNodes: [
- {
- id: 'test.py.repo.["run_failing_asset"]',
- opNames: ['run_failing_asset'],
- repository,
- partitionStats: {
- __typename: 'PartitionStats',
- numMaterialized: 8,
- numMaterializing: 0,
- numPartitions: 11,
- numFailed: 0,
- },
- assetKey: {
- path: ['run_failing_asset'],
- __typename: 'AssetKey',
- },
- assetChecksOrError: buildAssetChecks(),
- assetMaterializations: [
- {
- timestamp: '1666373060112',
- runId: 'e23b2cd2-7a4e-43d2-bdc6-892125375e8f',
- __typename: 'MaterializationEvent',
- },
- ],
+export const SingleAssetQueryLastRunFailed = buildQueryMock<
+ AssetGraphLiveQuery,
+ AssetGraphLiveQueryVariables
+>({
+ query: ASSETS_GRAPH_LIVE_QUERY,
+ variables: {assetKeys: [{path: ['run_failing_asset']}]},
+ data: {
+ assetNodes: [
+ buildAssetNode({
+ id: 'test.py.repo.["run_failing_asset"]',
+ opNames: ['run_failing_asset'],
+ repository,
+ partitionStats: buildPartitionStats({
+ numMaterialized: 8,
+ numMaterializing: 0,
+ numPartitions: 11,
+ numFailed: 0,
+ }),
+ assetKey: buildAssetKey({
+ path: ['run_failing_asset'],
+ }),
+ assetChecksOrError: buildAssetChecks(),
+ assetMaterializations: [
+ buildMaterializationEvent({
+ timestamp: '1666373060112',
+ runId: 'e23b2cd2-7a4e-43d2-bdc6-892125375e8f',
+ }),
+ ],
- freshnessInfo: null,
- assetObservations: [],
- staleStatus: StaleStatus.MISSING,
- staleCauses: [],
- __typename: 'AssetNode',
- },
- ],
- assetsLatestInfo: [
- {
- id: 'test.py.repo.["run_failing_asset"]',
- assetKey: {
- path: ['run_failing_asset'],
- __typename: 'AssetKey',
- },
- unstartedRunIds: [],
- inProgressRunIds: [],
- latestRun: {
- id: '4678865f-6191-4a35-bb47-2122d57ec9a6',
- status: RunStatus.FAILURE,
- endTime: 1669067250.48091,
- __typename: 'Run',
- },
- __typename: 'AssetLatestInfo',
- },
- ],
- },
+ freshnessInfo: null,
+ assetObservations: [],
+ staleStatus: StaleStatus.MISSING,
+ staleCauses: [],
+ }),
+ ],
+ assetsLatestInfo: [
+ buildAssetLatestInfo({
+ id: 'test.py.repo.["run_failing_asset"]',
+ assetKey: buildAssetKey({
+ path: ['run_failing_asset'],
+ }),
+ unstartedRunIds: [],
+ inProgressRunIds: [],
+ latestRun: buildRun({
+ id: '4678865f-6191-4a35-bb47-2122d57ec9a6',
+ status: RunStatus.FAILURE,
+ endTime: 1669067250.48091,
+ }),
+ }),
+ ],
},
-};
+});
-export const AssetCatalogTableMockAssets: Extract<
- AssetCatalogTableQuery['assetsOrError'],
- {__typename: 'AssetConnection'}
->['nodes'] = [
- {
+export const AssetCatalogTableMockAssets: Asset[] = [
+ buildAsset({
id: '["dashboards", "cost_dashboard"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['dashboards', 'cost_dashboard']}),
definition: null,
- },
- {
+ }),
+ buildAsset({
id: '["dashboards", "traffic_dashboard"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['dashboards', 'traffic_dashboard']}),
definition: null,
- },
- {
+ }),
+ buildAsset({
id: 'test.py.repo.["good_asset"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['good_asset']}),
definition: buildAssetNode({
id: 'test.py.repo.["good_asset"]',
@@ -279,10 +255,9 @@ export const AssetCatalogTableMockAssets: Extract<
'This is a super long description that could involve some level of SQL and is just generally very long',
repository,
}),
- },
- {
+ }),
+ buildAsset({
id: 'test.py.repo.["late_asset"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['late_asset']}),
definition: buildAssetNode({
id: 'test.py.repo.["late_asset"]',
@@ -298,10 +273,9 @@ export const AssetCatalogTableMockAssets: Extract<
description: null,
repository,
}),
- },
- {
+ }),
+ buildAsset({
id: 'test.py.repo.["run_failing_asset"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['run_failing_asset']}),
definition: buildAssetNode({
id: 'test.py.repo.["run_failing_asset"]',
@@ -315,10 +289,9 @@ export const AssetCatalogTableMockAssets: Extract<
computeKind: 'sql',
repository,
}),
- },
- {
+ }),
+ buildAsset({
id: 'test.py.repo.["asset_with_a_very_long_key_that_will_require_truncation"]',
- __typename: 'Asset',
key: buildAssetKey({path: ['asset_with_a_very_long_key_that_will_require_truncation']}),
definition: buildAssetNode({
id: 'test.py.repo.["asset_with_a_very_long_key_that_will_require_truncation"]',
@@ -329,20 +302,17 @@ export const AssetCatalogTableMockAssets: Extract<
computeKind: 'ipynb',
repository,
}),
- },
+ }),
];
-export const AssetCatalogTableMock: MockedResponse = {
- request: {
- query: ASSET_CATALOG_TABLE_QUERY,
- },
- result: {
- data: {
- __typename: 'Query',
- assetsOrError: {
- __typename: 'AssetConnection',
- nodes: AssetCatalogTableMockAssets,
- },
- },
+export const AssetCatalogTableMock = buildQueryMock<
+ AssetCatalogTableQuery,
+ AssetCatalogTableQueryVariables
+>({
+ query: ASSET_CATALOG_TABLE_QUERY,
+ data: {
+ assetsOrError: buildAssetConnection({
+ nodes: AssetCatalogTableMockAssets,
+ }),
},
-};
+});
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/__tests__/buildAssetTabs.test.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/__tests__/buildAssetTabs.test.tsx
index 22f6daa022812..df9a6c4bafeaf 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/__tests__/buildAssetTabs.test.tsx
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/__tests__/buildAssetTabs.test.tsx
@@ -9,6 +9,8 @@ import {
buildConfigTypeField,
buildDimensionPartitionKeys,
buildPartitionDefinition,
+ buildRegularConfigType,
+ buildRegularDagsterType,
buildRepository,
buildRepositoryLocation,
} from '../../graphql/types';
@@ -49,18 +51,11 @@ describe('buildAssetTabs', () => {
name: 'dagster_test.toys.repo',
}),
}),
- description: null,
- graphName: null,
targetingInstigators: [],
opNames: ['eager_downstream_3_partitioned'],
- opVersion: null,
jobNames: ['__ASSET_JOB_0'],
autoMaterializePolicy,
- backfillPolicy: null,
- freshnessPolicy: null,
- requiredResources: [],
hasMaterializePermission: true,
- computeKind: null,
isPartitioned: true,
isObservable: false,
isExecutable: true,
@@ -68,89 +63,66 @@ describe('buildAssetTabs', () => {
assetKey: buildAssetKey({
path: ['eager_downstream_3_partitioned'],
}),
- metadataEntries: [],
- type: {
- __typename: 'RegularDagsterType',
+ type: buildRegularDagsterType({
key: 'Any',
name: 'Any',
displayName: 'Any',
- description: null,
isNullable: false,
isList: false,
isBuiltin: true,
isNothing: false,
- metadataEntries: [],
inputSchemaType: buildCompositeConfigType({
key: 'Selector.f2fe6dfdc60a1947a8f8e7cd377a012b47065bc4',
- description: null,
isSelector: true,
typeParamKeys: [],
fields: [
buildConfigTypeField({
name: 'json',
- description: null,
isRequired: true,
configTypeKey: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
- defaultValueAsJson: null,
}),
buildConfigTypeField({
name: 'pickle',
- description: null,
isRequired: true,
configTypeKey: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
- defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
}),
buildConfigTypeField({
name: 'value',
- description: null,
isRequired: true,
configTypeKey: 'Any',
- defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
}),
],
recursiveConfigTypes: [
buildCompositeConfigType({
key: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
- description: null,
isSelector: false,
- typeParamKeys: [],
fields: [
buildConfigTypeField({
name: 'path',
- description: null,
isRequired: true,
configTypeKey: 'String',
- defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
}),
],
}),
],
}),
- outputSchemaType: null,
- innerTypes: [],
- },
+ }),
});
// Copied from browser
- const definitionWithoutPartition: AssetViewDefinitionNodeFragment = {
+ const definitionWithoutPartition = buildAssetNode({
id: 'dagster_test.toys.repo.auto_materialize_repo_1.["lazy_downstream_1"]',
groupName: 'default',
partitionDefinition: null,
partitionKeysByDimension: [],
- repository: {
+ repository: buildRepository({
id: '4d9fd77c222a797eb8427fcbe1968799ebc24de8',
name: 'auto_materialize_repo_1',
- location: {
+ location: buildRepositoryLocation({
id: 'dagster_test.toys.repo',
name: 'dagster_test.toys.repo',
- __typename: 'RepositoryLocation',
- },
- __typename: 'Repository',
- },
- __typename: 'AssetNode',
+ }),
+ }),
description: null,
graphName: null,
targetingInstigators: [],
@@ -161,34 +133,30 @@ describe('buildAssetTabs', () => {
backfillPolicy: null,
freshnessPolicy: null,
requiredResources: [],
- configField: {
+ configField: buildConfigTypeField({
name: 'config',
isRequired: false,
- configType: {
+ configType: buildRegularConfigType({
givenName: 'Any',
- __typename: 'RegularConfigType',
key: 'Any',
description: null,
isSelector: false,
typeParamKeys: [],
recursiveConfigTypes: [],
- },
- __typename: 'ConfigTypeField',
- },
+ }),
+ }),
hasMaterializePermission: true,
computeKind: null,
isPartitioned: false,
isObservable: false,
isExecutable: true,
isSource: false,
- assetKey: {
+ assetKey: buildAssetKey({
path: ['lazy_downstream_1'],
- __typename: 'AssetKey',
- },
+ }),
metadataEntries: [],
owners: [],
- type: {
- __typename: 'RegularDagsterType',
+ type: buildRegularDagsterType({
key: 'Any',
name: 'Any',
displayName: 'Any',
@@ -198,78 +166,70 @@ describe('buildAssetTabs', () => {
isBuiltin: true,
isNothing: false,
metadataEntries: [],
- inputSchemaType: {
+ inputSchemaType: buildCompositeConfigType({
key: 'Selector.f2fe6dfdc60a1947a8f8e7cd377a012b47065bc4',
description: null,
isSelector: true,
typeParamKeys: [],
fields: [
- {
+ buildConfigTypeField({
name: 'json',
description: null,
isRequired: true,
configTypeKey: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
- },
- {
+ }),
+ buildConfigTypeField({
name: 'pickle',
description: null,
isRequired: true,
configTypeKey: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
- },
- {
+ }),
+ buildConfigTypeField({
name: 'value',
description: null,
isRequired: true,
configTypeKey: 'Any',
defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
- },
+ }),
],
- __typename: 'CompositeConfigType',
recursiveConfigTypes: [
- {
+ buildCompositeConfigType({
key: 'Shape.4b53b73df342381d0d05c5f36183dc99cb9676e2',
description: null,
isSelector: false,
typeParamKeys: [],
fields: [
- {
+ buildConfigTypeField({
name: 'path',
description: null,
isRequired: true,
configTypeKey: 'String',
defaultValueAsJson: null,
- __typename: 'ConfigTypeField',
- },
+ }),
],
- __typename: 'CompositeConfigType',
- },
- {
+ }),
+ buildRegularConfigType({
givenName: 'String',
- __typename: 'RegularConfigType',
key: 'String',
description: '',
isSelector: false,
typeParamKeys: [],
- },
- {
+ }),
+ buildRegularConfigType({
givenName: 'Any',
- __typename: 'RegularConfigType',
key: 'Any',
description: null,
isSelector: false,
typeParamKeys: [],
- },
+ }),
],
- },
+ }),
outputSchemaType: null,
innerTypes: [],
- },
- };
+ }),
+ });
const params = {};
it('shows all tabs', () => {
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetNodeDefinition.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetNodeDefinition.types.ts
index fc7964be4fd23..702d98a4372a2 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetNodeDefinition.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetNodeDefinition.types.ts
@@ -14,6 +14,7 @@ export type AssetNodeDefinitionFragment = {
isSource: boolean;
isExecutable: boolean;
hasMaterializePermission: boolean;
+ changedReasons: Array;
computeKind: string | null;
isPartitioned: boolean;
isObservable: boolean;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetView.types.ts b/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetView.types.ts
index 8ecbe6adf02f5..c3890cb9124d1 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetView.types.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/assets/types/AssetView.types.ts
@@ -30,6 +30,7 @@ export type AssetViewDefinitionQuery = {
isSource: boolean;
isExecutable: boolean;
hasMaterializePermission: boolean;
+ changedReasons: Array;
computeKind: string | null;
isPartitioned: boolean;
isObservable: boolean;
@@ -15855,6 +15856,7 @@ export type AssetViewDefinitionNodeFragment = {
isSource: boolean;
isExecutable: boolean;
hasMaterializePermission: boolean;
+ changedReasons: Array;
computeKind: string | null;
isPartitioned: boolean;
isObservable: boolean;
diff --git a/js_modules/dagster-ui/packages/ui-core/src/testing/mocking.ts b/js_modules/dagster-ui/packages/ui-core/src/testing/mocking.ts
index 29a83a024ab98..54aa8c72f5c21 100644
--- a/js_modules/dagster-ui/packages/ui-core/src/testing/mocking.ts
+++ b/js_modules/dagster-ui/packages/ui-core/src/testing/mocking.ts
@@ -1,11 +1,11 @@
-import {DocumentNode} from '@apollo/client';
+import {DocumentNode, OperationVariables} from '@apollo/client';
import {MockedResponse} from '@apollo/client/testing';
import deepmerge from 'deepmerge';
import {GraphQLError} from 'graphql';
export function buildQueryMock<
TQuery extends {__typename: 'Query'},
- TVariables extends Record,
+ TVariables extends OperationVariables,
>({
query,
variables,
@@ -14,7 +14,7 @@ export function buildQueryMock<
...rest
}: Partial> & {
query: DocumentNode;
- variables: TVariables;
+ variables?: TVariables;
data?: Omit;
errors?: ReadonlyArray;
}): MockedResponse {