Skip to content

Commit cc1f15f

Browse files
authored
Feat: Display node for CTE (#915)
1 parent 24a6e66 commit cc1f15f

16 files changed

Lines changed: 387 additions & 153 deletions

File tree

tests/web/test_main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,13 +484,13 @@ def test_get_environments(project_context: Context) -> None:
484484

485485
def test_get_lineage(web_sushi_context: Context) -> None:
486486
response = client.get("/api/lineage/sushi.waiters/ds")
487-
assert response.status_code == 200
488487

488+
assert response.status_code == 200
489489
assert response.json() == {
490490
"sushi.waiters": {
491491
"ds": {
492492
"source": """SELECT DISTINCT
493-
<b>CAST(o.ds AS TEXT) AS ds</b>
493+
CAST(o.ds AS TEXT) AS ds
494494
FROM (
495495
SELECT
496496
CAST(NULL AS INT) AS id,
@@ -504,12 +504,14 @@ def test_get_lineage(web_sushi_context: Context) -> None:
504504
) AS o /* source: sushi.orders */
505505
WHERE
506506
o.ds <= '1970-01-01' AND o.ds >= '1970-01-01'""",
507+
"expression": "CAST(o.ds AS TEXT) AS ds",
507508
"models": {"sushi.orders": ["ds"]},
508509
}
509510
},
510511
"sushi.orders": {
511512
"ds": {
512-
"source": "SELECT\n <b>CAST(NULL AS TEXT) AS ds</b>\nFROM (VALUES\n (1)) AS t(dummy)",
513+
"source": "SELECT\n CAST(NULL AS TEXT) AS ds\nFROM (VALUES\n (1)) AS t(dummy)",
514+
"expression": "CAST(NULL AS TEXT) AS ds",
513515
"models": {},
514516
}
515517
},

web/client/openapi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,7 @@
985985
"type": "object",
986986
"properties": {
987987
"source": { "title": "Source", "type": "string" },
988+
"expression": { "title": "Expression", "type": "string" },
988989
"models": {
989990
"title": "Models",
990991
"type": "object",

web/client/src/api/instance.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export async function fetchAPI<T = any, B extends object = any>(
6565
})
6666
.then(async response => {
6767
const headerContentType = response.headers.get('Content-Type')
68-
6968
if (headerContentType == null)
7069
return { ok: false, message: 'Empty response' }
7170
if (response.status >= 400) {
@@ -75,10 +74,10 @@ export async function fetchAPI<T = any, B extends object = any>(
7574
json.status = json.status ?? response.status
7675

7776
throw json
78-
} catch (error) {
77+
} catch (error: any) {
7978
throw {
8079
message: response.statusText,
81-
status: response.status,
80+
...error,
8281
} as unknown as Error
8382
}
8483
}

web/client/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
height: 0.2rem;
88
}
99

10+
.scrollbar--horizontal-md::-webkit-scrollbar {
11+
height: 0.4rem;
12+
}
13+
1014
.scrollbar--vertical::-webkit-scrollbar {
1115
width: 0.2rem;
1216
}

web/client/src/library/components/documentation/Documentation.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import { MinusCircleIcon, PlusCircleIcon } from '@heroicons/react/24/solid'
77
import { EnumFileExtensions } from '@models/file'
88
import { isFalse, isString, isTrue, toDateFormat } from '@utils/index'
99
import clsx from 'clsx'
10-
import { useNavigate } from 'react-router-dom'
11-
import { EnumRoutes } from '~/routes'
12-
import { ModelSQLMeshModel } from '@models/sqlmesh-model'
10+
import { type ModelSQLMeshModel } from '@models/sqlmesh-model'
1311
import { ModelColumns } from '@components/graph/Graph'
12+
import { useLineageFlow } from '@components/graph/context'
1413

1514
const Documentation = function Documentation({
1615
model,
@@ -27,12 +26,10 @@ const Documentation = function Documentation({
2726
withDescription?: boolean
2827
withColumns?: boolean
2928
}): JSX.Element {
30-
const navigate = useNavigate()
29+
const { handleClickModel } = useLineageFlow()
3130

3231
const modelExtensions = useSQLMeshModelExtensions(model.path, model => {
33-
navigate(
34-
`${EnumRoutes.IdeDocsModels}/${ModelSQLMeshModel.encodeName(model.name)}`,
35-
)
32+
handleClickModel?.(model.name)
3633
})
3734

3835
return (
@@ -86,6 +83,7 @@ const Documentation = function Documentation({
8683
columns={model.columns}
8784
disabled={model?.type === 'python'}
8885
withHandles={false}
86+
withSource={false}
8987
limit={10}
9088
/>
9189
</Section>

web/client/src/library/components/editor/Editor.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
width: 0.2rem;
3737
}
3838

39+
.scrollbar--vertical-md .cm-scroller::-webkit-scrollbar {
40+
width: 0.4rem;
41+
}
42+
43+
.scrollbar--horizontal-md .cm-scroller::-webkit-scrollbar {
44+
height: 0.4rem;
45+
}
46+
3947
.cm-editor .cm-scroller::-webkit-scrollbar-track {
4048
background-color: transparent;
4149
}
@@ -51,6 +59,11 @@
5159
background: var(--color-theme) !important;
5260
}
5361

62+
.sqlmesh-expression {
63+
color: var(--color-primary-500);
64+
background: var(--color-primary-10);
65+
}
66+
5467
.sqlmesh-model,
5568
.sqlmesh-model > span {
5669
display: inline-block;

web/client/src/library/components/editor/EditorCode.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ function CodeEditorDefault({
4444
type,
4545
content = '',
4646
children,
47+
className,
4748
}: {
4849
type: FileExtensions
4950
content: string
51+
className?: string
5052
children: (options: {
5153
extensions: Extension[]
5254
content: string
@@ -65,7 +67,7 @@ function CodeEditorDefault({
6567
}, [type, mode])
6668

6769
return (
68-
<div className="flex overflow-auto h-full">
70+
<div className={clsx('flex w-full h-full', className)}>
6971
{children({ extensions, content })}
7072
</div>
7173
)
@@ -75,9 +77,11 @@ function CodeEditorSQLMesh({
7577
type,
7678
content = '',
7779
children,
80+
className,
7881
}: {
7982
type: FileExtensions
8083
content?: string
84+
className?: string
8185
children: (options: {
8286
extensions: Extension[]
8387
content: string
@@ -110,7 +114,6 @@ function CodeEditorSQLMesh({
110114

111115
const extensions = useMemo(() => {
112116
return [
113-
mode === EnumColorScheme.Dark ? dracula : tomorrow,
114117
mode === EnumColorScheme.Dark ? dracula : tomorrow,
115118
type === EnumFileExtensions.Python && python(),
116119
type === EnumFileExtensions.YAML && StreamLanguage.define(yaml),
@@ -140,7 +143,7 @@ function CodeEditorSQLMesh({
140143
}, [content])
141144

142145
return (
143-
<div className="flex overflow-auto h-full">
146+
<div className={clsx('flex w-full h-full', className)}>
144147
{children({ extensions, content })}
145148
</div>
146149
)
@@ -214,9 +217,7 @@ export function useSQLMeshModelExtensions(
214217
const files = useStoreFileTree(s => s.files)
215218

216219
const extensions = useMemo(() => {
217-
if (path == null) return []
218-
219-
const model = models.get(path)
220+
const model = path == null ? undefined : models.get(path)
220221
const columns =
221222
lineage == null
222223
? new Set<string>()

web/client/src/library/components/editor/extensions/index.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { syntaxTree } from '@codemirror/language'
2-
import { type Extension } from '@codemirror/state'
2+
import { RangeSetBuilder, type Extension } from '@codemirror/state'
33
import {
44
ViewPlugin,
55
type DecorationSet,
@@ -128,6 +128,53 @@ export function HoverTooltip(
128128
)
129129
}
130130

131+
export function SqlMeshExpression(expression: string): Extension {
132+
return ViewPlugin.fromClass(
133+
class SqlMeshModelView {
134+
decorations: DecorationSet = Decoration.set([])
135+
136+
constructor(readonly view: EditorView) {
137+
this.decorations = markExpressionLine(expression, view)
138+
}
139+
140+
update(viewUpdate: ViewUpdate): void {
141+
this.decorations = markExpressionLine(expression, viewUpdate.view)
142+
}
143+
},
144+
{
145+
decorations: value => value.decorations,
146+
},
147+
)
148+
}
149+
150+
function markExpressionLine(
151+
expression: string,
152+
view: EditorView,
153+
): DecorationSet {
154+
const mark = Decoration.line({
155+
attributes: {
156+
id: expression,
157+
class: 'sqlmesh-expression',
158+
},
159+
})
160+
161+
const builder = new RangeSetBuilder<Decoration>()
162+
163+
for (const { from, to } of view.visibleRanges) {
164+
for (let pos = from; pos <= to; ) {
165+
const line = view.state.doc.lineAt(pos)
166+
167+
if (line.text.includes(expression)) {
168+
builder.add(line.from, line.from, mark)
169+
}
170+
171+
pos = line.to + 1
172+
}
173+
}
174+
175+
return builder.finish()
176+
}
177+
131178
function getDecorations(
132179
models: Map<string, ModelSQLMeshModel>,
133180
view: EditorView,

0 commit comments

Comments
 (0)