Skip to content

Commit 4137773

Browse files
authored
feat(vscode): add ability to turn on/off columns (#4826)
1 parent 83137ff commit 4137773

File tree

7 files changed

+162
-3
lines changed

7 files changed

+162
-3
lines changed

vscode/extension/tests/broken_project.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ test('working project, then broken through adding double model, then refixed', a
137137
if (activeFrame) {
138138
try {
139139
await activeFrame
140-
.getByText('raw.demographics')
140+
.getByText('sushi.customers')
141141
.waitFor({ timeout: 1000 })
142142
raw_demographicsCount++
143143
} catch {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { test, expect } from '@playwright/test'
2+
import path from 'path'
3+
import fs from 'fs-extra'
4+
import os from 'os'
5+
import { openLineageView, SUSHI_SOURCE_PATH } from './utils'
6+
import { startCodeServer, stopCodeServer } from './utils_code_server'
7+
8+
test('Settings button is visible in the lineage view', async ({ page }) => {
9+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'vscode-test-sushi-'))
10+
await fs.copy(SUSHI_SOURCE_PATH, tempDir)
11+
12+
const context = await startCodeServer({
13+
tempDir,
14+
placeFileWithPythonInterpreter: true,
15+
})
16+
17+
try {
18+
await page.goto(`http://127.0.0.1:${context.codeServerPort}`)
19+
20+
await page.waitForSelector('text=models')
21+
22+
// Click on the models folder, excluding external_models
23+
await page
24+
.getByRole('treeitem', { name: 'models', exact: true })
25+
.locator('a')
26+
.click()
27+
// Open the waiters.py model
28+
await page
29+
.getByRole('treeitem', { name: 'waiters.py', exact: true })
30+
.locator('a')
31+
.click()
32+
await page.waitForSelector('text=Loaded SQLMesh Context')
33+
34+
// Open lineage
35+
await openLineageView(page)
36+
37+
const iframes = page.locator('iframe')
38+
const iframeCount = await iframes.count()
39+
let settingsCount = 0
40+
41+
for (let i = 0; i < iframeCount; i++) {
42+
const iframe = iframes.nth(i)
43+
const contentFrame = iframe.contentFrame()
44+
if (contentFrame) {
45+
const activeFrame = contentFrame.locator('#active-frame').contentFrame()
46+
if (activeFrame) {
47+
try {
48+
await activeFrame
49+
.getByRole('button', {
50+
name: 'Settings',
51+
})
52+
.waitFor({ timeout: 1000 })
53+
settingsCount++
54+
} catch {
55+
// Continue to next iframe if this one doesn't have the error
56+
continue
57+
}
58+
}
59+
}
60+
}
61+
62+
expect(settingsCount).toBeGreaterThan(0)
63+
} finally {
64+
await stopCodeServer(context)
65+
}
66+
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from 'react'
2+
3+
/**
4+
* CogIcon as taken from https://heroicons.com/. Slightly modified to remove fill color.
5+
*
6+
* @param props - SVG props
7+
* @returns SVG element
8+
*/
9+
export function CogIcon(props: React.SVGProps<SVGSVGElement>): JSX.Element {
10+
return (
11+
<svg
12+
xmlns="http://www.w3.org/2000/svg"
13+
aria-hidden="true"
14+
className="h-6 w-6"
15+
data-slot="icon"
16+
viewBox="0 0 24 24"
17+
{...props}
18+
>
19+
<path d="M17.004 10.407c.138.435-.216.842-.672.842h-3.465a.75.75 0 0 1-.65-.375l-1.732-3c-.229-.396-.053-.907.393-1.004a5.252 5.252 0 0 1 6.126 3.537ZM8.12 8.464c.307-.338.838-.235 1.066.16l1.732 3a.75.75 0 0 1 0 .75l-1.732 3c-.229.397-.76.5-1.067.161A5.23 5.23 0 0 1 6.75 12a5.23 5.23 0 0 1 1.37-3.536Zm2.758 8.666c-.447-.098-.623-.608-.394-1.004l1.733-3.002a.75.75 0 0 1 .65-.375h3.465c.457 0 .81.407.672.842a5.252 5.252 0 0 1-6.126 3.539Z" />
20+
<path
21+
fillRule="evenodd"
22+
d="M21 12.75a.75.75 0 1 0 0-1.5h-.783a8.22 8.22 0 0 0-.237-1.357l.734-.267a.75.75 0 1 0-.513-1.41l-.735.268a8.24 8.24 0 0 0-.689-1.192l.6-.503a.75.75 0 1 0-.964-1.149l-.6.504a8.3 8.3 0 0 0-1.054-.885l.391-.678a.75.75 0 1 0-1.299-.75l-.39.676a8.188 8.188 0 0 0-1.295-.47l.136-.77a.75.75 0 0 0-1.477-.26l-.136.77a8.36 8.36 0 0 0-1.377 0l-.136-.77a.75.75 0 1 0-1.477.26l.136.77c-.448.121-.88.28-1.294.47l-.39-.676a.75.75 0 0 0-1.3.75l.392.678a8.29 8.29 0 0 0-1.054.885l-.6-.504a.75.75 0 1 0-.965 1.149l.6.503a8.243 8.243 0 0 0-.689 1.192L3.8 8.216a.75.75 0 1 0-.513 1.41l.735.267a8.222 8.222 0 0 0-.238 1.356h-.783a.75.75 0 0 0 0 1.5h.783c.042.464.122.917.238 1.356l-.735.268a.75.75 0 0 0 .513 1.41l.735-.268c.197.417.428.816.69 1.191l-.6.504a.75.75 0 0 0 .963 1.15l.601-.505c.326.323.679.62 1.054.885l-.392.68a.75.75 0 0 0 1.3.75l.39-.679c.414.192.847.35 1.294.471l-.136.77a.75.75 0 0 0 1.477.261l.137-.772a8.332 8.332 0 0 0 1.376 0l.136.772a.75.75 0 1 0 1.477-.26l-.136-.771a8.19 8.19 0 0 0 1.294-.47l.391.677a.75.75 0 0 0 1.3-.75l-.393-.679a8.29 8.29 0 0 0 1.054-.885l.601.504a.75.75 0 0 0 .964-1.15l-.6-.503c.261-.375.492-.774.69-1.191l.735.267a.75.75 0 1 0 .512-1.41l-.734-.267c.115-.439.195-.892.237-1.356h.784Zm-2.657-3.06a6.744 6.744 0 0 0-1.19-2.053 6.784 6.784 0 0 0-1.82-1.51A6.705 6.705 0 0 0 12 5.25a6.8 6.8 0 0 0-1.225.11 6.7 6.7 0 0 0-2.15.793 6.784 6.784 0 0 0-2.952 3.489.76.76 0 0 1-.036.098A6.74 6.74 0 0 0 5.251 12a6.74 6.74 0 0 0 3.366 5.842l.009.005a6.704 6.704 0 0 0 2.18.798l.022.003a6.792 6.792 0 0 0 2.368-.004 6.704 6.704 0 0 0 2.205-.811 6.785 6.785 0 0 0 1.762-1.484l.009-.01.009-.01a6.743 6.743 0 0 0 1.18-2.066c.253-.707.39-1.469.39-2.263a6.74 6.74 0 0 0-.408-2.309Z"
23+
clipRule="evenodd"
24+
/>
25+
</svg>
26+
)
27+
}

vscode/react/src/components/graph/Graph.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ react-flow__attribution {
2929
box-shadow: none;
3030
border: var(--vscode-button-border);
3131
background: var(--vscode-button-background);
32+
color: var(--vscode-foreground);
3233
}
3334
.react-flow__controls-button:hover {
3435
background: var(--vscode-button-hoverBackground);

vscode/react/src/components/graph/ModelLineage.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { Popover } from '@headlessui/react'
3636
import ModelLineageDetails from './ModelLineageDetails'
3737
import { Divider } from '@/components/divider/Divider'
3838
import { type ModelLineageApiLineageModelNameGet200 } from '@/api/client'
39+
import { SettingsControl } from '@/components/graph/SettingsControl'
3940
import './Graph.css'
4041

4142
const WITH_COLUMNS_LIMIT = 30
@@ -203,6 +204,7 @@ function ModelColumnLineage(): JSX.Element {
203204
showControls,
204205
handleError,
205206
setActiveNodes,
207+
setWithColumns,
206208
} = useLineageFlow()
207209

208210
const { setCenter } = useReactFlow()
@@ -386,7 +388,12 @@ function ModelColumnLineage(): JSX.Element {
386388
<Controls
387389
className="bg-light p-1 rounded-md !border-none !shadow-lg"
388390
showInteractive={false}
389-
/>
391+
>
392+
<SettingsControl
393+
showColumns={withColumns}
394+
onWithColumnsChange={setWithColumns}
395+
/>
396+
</Controls>
390397
<Background
391398
variant={BackgroundVariant.Cross}
392399
gap={32}

vscode/react/src/components/graph/ModelNode.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ export default function ModelNode({
141141
const isModelExternal = nodeType === EnumLineageNodeModelType.external
142142
const isModelSeed = nodeType === EnumLineageNodeModelType.seed
143143
const isModelUnknown = nodeType === EnumLineageNodeModelType.unknown
144-
const showColumns = isArrayNotEmpty(columns) && isFalse(hasHighlightedNodes)
144+
const showColumns =
145+
nodeData.withColumns &&
146+
isArrayNotEmpty(columns) &&
147+
isFalse(hasHighlightedNodes)
145148
const isActiveNode =
146149
selectedNodes.size > 0 || activeNodes.size > 0 || withConnected
147150
? isSelected ||
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Menu } from '@headlessui/react'
2+
import { CheckIcon } from '@heroicons/react/24/outline'
3+
import { CogIcon } from '@/components/graph/CogIcon'
4+
import clsx from 'clsx'
5+
6+
interface SettingsControlProps {
7+
showColumns: boolean
8+
onWithColumnsChange: (value: boolean) => void
9+
}
10+
11+
export function SettingsControl({
12+
showColumns,
13+
onWithColumnsChange,
14+
}: SettingsControlProps): JSX.Element {
15+
return (
16+
<Menu
17+
as="div"
18+
className="relative"
19+
>
20+
<Menu.Button
21+
className="react-flow__controls-button"
22+
title="Settings"
23+
>
24+
<CogIcon
25+
className="h-3 w-3"
26+
aria-hidden="true"
27+
/>
28+
</Menu.Button>
29+
<Menu.Items className="absolute bottom-0 left-full ml-2 w-56 origin-bottom-left divide-y bg-theme shadow-lg focus:outline-none z-50">
30+
<Menu.Item>
31+
{({ active }) => (
32+
<button
33+
className={clsx(
34+
'group flex w-full items-center px-2 py-1 text-sm',
35+
'text-[var(--vscode-button-foreground)]',
36+
active
37+
? 'bg-[var(--vscode-button-background)]'
38+
: 'bg-[var(--vscode-button-hoverBackground)]',
39+
)}
40+
onClick={() => onWithColumnsChange(!showColumns)}
41+
>
42+
<span className="flex-1 text-left">Show Columns</span>
43+
{showColumns && (
44+
<CheckIcon
45+
className="h-4 w-4 text-primary-500"
46+
aria-hidden="true"
47+
/>
48+
)}
49+
</button>
50+
)}
51+
</Menu.Item>
52+
</Menu.Items>
53+
</Menu>
54+
)
55+
}

0 commit comments

Comments
 (0)