Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c951b32
feat(cll): add --downstream-of-breaking CLI flag
danyelf May 18, 2026
4e45283
feat(cll): compute and publish whole-model impact sets
danyelf May 19, 2026
be8c0f4
feat(cll): paint whole-model treatment across NodeView, LineageNode, …
danyelf May 19, 2026
97a5a55
story(cll): whole-model impact stories
danyelf May 19, 2026
69dd445
fix(cll): gate whole-model treatment on --downstream-of-breaking + dr…
danyelf May 19, 2026
21b3227
refactor(cll): rename --downstream-of-breaking to --whole-model-impact
danyelf May 19, 2026
90dcd51
Potential fix for pull request finding
danyelf May 19, 2026
fd9ed26
fix(cll): allocate fresh empty sets in computeWholeModelImpact
danyelf May 19, 2026
8f23541
feat(cll): rename whole-model badges from TABLE/ADD to OVERALL Δ / ADD Δ
danyelf May 19, 2026
8f82b8b
fix(cll): make TreatmentChip sx prop type-safe under array form
danyelf May 19, 2026
2741687
feat(cll): label only column-only kinds; drop whole-model graph badges
danyelf May 19, 2026
d91fde1
refactor(cll): split badge metadata by surface; drop dead code after …
danyelf May 19, 2026
1481b1c
Merge branch 'main' into feature/drc-3341-downstream-of-breaking-v2
danyelf May 20, 2026
1c9bf5c
Merge branch 'main' into feature/drc-3341-downstream-of-breaking-v2
danyelf May 20, 2026
c9f86cf
refactor(cll): clean up whole-model treatment surface per PR #1381 re…
danyelf May 20, 2026
d3e51c6
Merge branch 'main' into feature/drc-3341-downstream-of-breaking-v2
danyelf May 20, 2026
5f7007b
fix(lineage): unify NodeView title tooltip across chip + plain branches
danyelf May 21, 2026
a5a2c1f
fix(lineage): include whole-model treatment in node card title tooltip
danyelf May 21, 2026
24547f8
Merge branch 'main' into feature/drc-3341-downstream-of-breaking-v2
danyelf May 21, 2026
28e2c04
fix(lineage): correct dark-mode detection + unify title-row tooltip
danyelf May 21, 2026
e02c118
fix(check): use useThemeColors() for dark-mode detection on Check page
danyelf May 21, 2026
c6554d9
fix(lineage): address second-pass review findings
danyelf May 21, 2026
8aa8900
refactor(lineage): collapse whole-model treatment pipeline
danyelf May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions js/packages/storybook/stories/lineage/WholeModelImpact.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import type {
NodeViewNodeData,
NodeViewProps,
SchemaViewProps,
} from "@datarecce/ui/advanced";
import { NodeView } from "@datarecce/ui/advanced";
import { LineageNode, NodeTag } from "@datarecce/ui/primitives";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ReactFlowProvider } from "@xyflow/react";
import { fn } from "storybook/test";

/**
* @file WholeModelImpact.stories.tsx
* @description Visual smoke for the DRC-3341 whole-model treatment.
*
* Six stories, one dimension of variation each:
* - NodeView: changed, impacted, additive title chip + badge + stripe
* - LineageNode: changed, impacted, additive graph badge
*/

// =============================================================================
// STUB COMPONENTS for NodeView stories
// =============================================================================

function StubSchemaView({ base, current }: SchemaViewProps) {
return (
<Box sx={{ p: 2 }}>
<Typography variant="body2" sx={{ color: "text.secondary" }}>
Schema diff view — base has{" "}
{base?.columns ? Object.keys(base.columns).length : 0} columns, current
has {current?.columns ? Object.keys(current.columns).length : 0} columns
</Typography>
</Box>
);
}

function StubNodeSqlView({
modelDetail,
}: {
node: NodeViewNodeData;
modelDetail?: NodeViewProps["modelDetail"];
}) {
const base = modelDetail?.base?.raw_code ?? "(none)";
const current = modelDetail?.current?.raw_code ?? "(none)";
return (
<Box sx={{ p: 2, fontFamily: "monospace", fontSize: "0.85rem" }}>
<Typography variant="subtitle2" gutterBottom>
Base
</Typography>
<pre style={{ margin: 0 }}>{base}</pre>
<Typography variant="subtitle2" gutterBottom sx={{ mt: 2 }}>
Current
</Typography>
<pre style={{ margin: 0 }}>{current}</pre>
</Box>
);
}

function ResourceTypeTag({ node }: { node: NodeViewNodeData }) {
return (
<NodeTag
resourceType={node.data.resourceType}
materialized={node.data.materialized}
/>
);
}

// =============================================================================
// NODEVIEW FIXTURE
// =============================================================================

const nodeViewNode: NodeViewNodeData = {
id: "model.jaffle_shop.stg_orders",
data: {
name: "stg_orders",
resourceType: "model",
changeStatus: "modified",
materialized: "table",
},
};

const nodeViewModelDetail: NodeViewProps["modelDetail"] = {
base: {
id: "stg_orders",
unique_id: "model.jaffle_shop.stg_orders",
name: "stg_orders",
raw_code: "SELECT * FROM raw.orders",
columns: {
order_id: { name: "order_id", type: "integer" },
customer_id: { name: "customer_id", type: "integer" },
order_date: { name: "order_date", type: "date" },
},
config: { materialized: "table" },
},
current: {
id: "stg_orders",
unique_id: "model.jaffle_shop.stg_orders",
name: "stg_orders",
raw_code: "SELECT * FROM raw.orders WHERE status != 'deleted'",
columns: {
order_id: { name: "order_id", type: "integer" },
customer_id: { name: "customer_id", type: "integer" },
order_date: { name: "order_date", type: "date" },
},
config: { materialized: "table" },
},
};

// =============================================================================
// LINEAGENODE FIXTURE
// =============================================================================

const lineageNodeBaseProps = {
id: "model.jaffle_shop.stg_orders",
data: {
label: "stg_orders",
resourceType: "model",
materialized: "table",
changeStatus: "modified" as const,
},
hasParents: true,
hasChildren: true,
showContent: true,
wholeModelImpact: true,
};

// =============================================================================
// META
// =============================================================================

const meta: Meta = {
title: "Lineage/WholeModelImpact",
parameters: {
docs: {
description: {
component:
"Visual smoke for the DRC-3341 whole-model treatment across two surfaces: NodeView (title chip + left stripe for whole-model kinds) and LineageNode (ADD / COLUMN graph badge for per-column kinds).",
},
},
},
};

export default meta;

// =============================================================================
// NODEVIEW STORIES — Layer 4.3 surfaces (title chip + badge + stripe)
// =============================================================================

type NodeViewStory = StoryObj<typeof NodeView>;

const nodeViewDecorator: Meta["decorators"] = [
(Story) => (
<Box sx={{ width: 400, height: 500, border: 1, borderColor: "divider" }}>
<Story />
</Box>
),
];

const baseNodeViewArgs: Partial<NodeViewProps<NodeViewNodeData>> = {
node: nodeViewNode,
modelDetail: nodeViewModelDetail,
onCloseNode: fn(),
isSingleEnv: false,
SchemaView: StubSchemaView,
NodeSqlView: StubNodeSqlView,
ResourceTypeTag,
wholeModelImpact: true,
};

/** Brown title chip + brown left stripe for a whole-model-changed model. */
export const NodeView_ChangedTitleChip: NodeViewStory = {
render: (args) => <NodeView {...(args as NodeViewProps<NodeViewNodeData>)} />,
args: {
...baseNodeViewArgs,
isWholeModelChanged: true,
},
decorators: nodeViewDecorator,
};

/** Amber title chip + amber left stripe for a whole-model-impacted model. */
export const NodeView_ImpactedTitleChip: NodeViewStory = {
render: (args) => <NodeView {...(args as NodeViewProps<NodeViewNodeData>)} />,
args: {
...baseNodeViewArgs,
isWholeModelImpacted: true,
},
decorators: nodeViewDecorator,
};

// Additive (non_breaking) changes don't get a NodeView treatment — they
// are per-column, not whole-table. The [ADD] badge still appears on the
// LineageNode graph (see LineageNode_AdditiveBadge below).

// =============================================================================
// LINEAGENODE STORIES — Layer 4.1 surface (graph badge)
// =============================================================================

type LineageNodeStory = StoryObj<typeof LineageNode>;

const lineageNodeDecorator: Meta["decorators"] = [
(Story) => (
<ReactFlowProvider>
<Box
sx={{
width: 320,
p: 2,
"& .react-flow__handle": { display: "none" },
}}
>
<Story />
</Box>
</ReactFlowProvider>
),
];

// Whole-model kinds (isWholeModelChanged / isWholeModelImpacted) intentionally
// render no graph badge on LineageNode — that signal lives on NodeView's
// title chip + stripe (see NodeView_ChangedTitleChip / NodeView_ImpactedTitleChip).

/** Green ADD badge for an additive-only model (change_category === "non_breaking"). */
export const LineageNode_AdditiveBadge: LineageNodeStory = {
render: (args) => <LineageNode {...args} />,
args: {
...lineageNodeBaseProps,
changeCategory: "non_breaking",
},
decorators: lineageNodeDecorator,
};

/** Brown COLUMN badge for a model with its own column-only change (change_category === "partial_breaking"). */
export const LineageNode_ColumnChangedBadge: LineageNodeStory = {
render: (args) => <LineageNode {...args} />,
args: {
...lineageNodeBaseProps,
changeCategory: "partial_breaking",
},
decorators: lineageNodeDecorator,
};

/** Amber COLUMN badge for a model downstream of a column-only change (isImpacted, no own change). */
export const LineageNode_ColumnImpactedBadge: LineageNodeStory = {
render: (args) => <LineageNode {...args} />,
args: {
...lineageNodeBaseProps,
isImpacted: true,
},
decorators: lineageNodeDecorator,
};
2 changes: 2 additions & 0 deletions js/packages/ui/src/api/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface RecceServerFlags {
disable_cll_cache: boolean;
impact_at_startup: boolean;
new_cll_experience: boolean;
/** Whole-model impact highlighting. Implies new_cll_experience. */
whole_model_impact: boolean;
}

/**
Expand Down
12 changes: 8 additions & 4 deletions js/packages/ui/src/components/check/CheckPageContentOss.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useRouter, useSearchParams } from "next/navigation";
import React, {
Expand All @@ -15,16 +14,21 @@ import React, {
} from "react";
import { cacheKeys, listChecks, reorderChecks } from "../../api";
import { useRouteConfig } from "../../contexts";
import { useApiConfig, useRecceCheckContext } from "../../hooks";
import {
useApiConfig,
useRecceCheckContext,
useThemeColors,
} from "../../hooks";
import { StateImporter } from "../app";
import { HSplit } from "../ui";
import { CheckDetailOss as CheckDetail } from "./CheckDetailOss";
import { CheckEmptyStateOss as CheckEmptyState } from "./CheckEmptyStateOss";
import { CheckListOss as CheckList } from "./CheckListOss";

export const CheckPageContentOss = (): ReactNode => {
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
// useTheme().palette.mode === "dark" does NOT work with this codebase's
// MUI colorSchemes setup — useThemeColors() is the correct accessor.
const { isDark } = useThemeColors();
const borderColor = isDark ? "grey.700" : "grey.300";
const router = useRouter();
const searchParams = useSearchParams();
Expand Down
7 changes: 4 additions & 3 deletions js/packages/ui/src/components/check/CheckPageLoadingOss.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import { useTheme } from "@mui/material/styles";
import React, { ReactNode } from "react";
import { useThemeColors } from "../../hooks";
import { HSplit } from "../ui";

/**
* Loading fallback - shows minimal UI while search params are being read
*/
export const CheckPageLoadingOss = (): ReactNode => {
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
// useTheme().palette.mode === "dark" does NOT work with this codebase's
// MUI colorSchemes setup — useThemeColors() is the correct accessor.
const { isDark } = useThemeColors();
const borderColor = isDark ? "grey.700" : "grey.300";

return (
Expand Down
12 changes: 11 additions & 1 deletion js/packages/ui/src/components/lineage/GraphNodeOss.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
type SelectMode,
} from "./nodes";
import { getIconForChangeStatus } from "./styles";
import { pickWholeModelFlags } from "./wholeModelTreatment";

// =============================================================================
// TYPES
Expand Down Expand Up @@ -282,6 +283,7 @@ function GraphNodeComponent(nodeProps: GraphNodeProps) {
const { isDark } = useThemeColors();

// Get context values
const lineageViewCtx = useLineageViewContextSafe();
const {
interactive,
selectNode,
Expand All @@ -297,8 +299,13 @@ function GraphNodeComponent(nodeProps: GraphNodeProps) {
cll,
impactedNodeIds,
newCllExperience,
} = useLineageViewContextSafe();
wholeModelImpact,
} = lineageViewCtx;
const isImpacted = newCllExperience ? impactedNodeIds.has(id) : false;
const { isWholeModelChanged, isWholeModelImpacted } = pickWholeModelFlags(
id,
lineageViewCtx,
);

// Computed state
const changeCategory = cll?.current.nodes[id]
Expand Down Expand Up @@ -356,6 +363,9 @@ function GraphNodeComponent(nodeProps: GraphNodeProps) {
// New CLL experience props
newCllExperience={newCllExperience}
isImpacted={isImpacted}
isWholeModelChanged={isWholeModelChanged}
isWholeModelImpacted={isWholeModelImpacted}
wholeModelImpact={wholeModelImpact}
// Interactive props
interactive={interactive}
selectMode={nodeSelectMode}
Expand Down
Loading
Loading