Skip to content

Commit 93b56e9

Browse files
authored
Feat: Display error tag on top level (#854)
1 parent 1261454 commit 93b56e9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1461
-992
lines changed

tests/web/test_main.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def test_create_directory_already_exists(project_tmp_path: Path) -> None:
270270

271271
response = client.post("/api/directories/new_dir")
272272
assert response.status_code == 422
273-
assert response.json() == {"detail": "Directory already exists"}
273+
assert response.json()["message"] == "Directory already exists"
274274

275275

276276
def test_rename_directory(project_tmp_path: Path) -> None:
@@ -317,6 +317,7 @@ def test_rename_directory_already_exists_not_empty(project_tmp_path: Path) -> No
317317

318318
response = client.post("/api/directories/new_dir", json={"new_path": "renamed_dir"})
319319
assert response.status_code == 422
320+
assert response.json()["message"] == "Unable to move a file"
320321
assert new_dir.exists()
321322

322323

@@ -328,6 +329,7 @@ def test_rename_directory_to_existing_file(project_tmp_path: Path) -> None:
328329

329330
response = client.post("/api/directories/new_dir", json={"new_path": "foo.txt"})
330331
assert response.status_code == 422
332+
assert response.json()["message"] == "Unable to move a file"
331333
assert new_dir.exists()
332334

333335

@@ -351,7 +353,7 @@ def test_delete_directory_not_a_directory(project_tmp_path: Path) -> None:
351353

352354
response = client.delete("/api/directories/foo.txt")
353355
assert response.status_code == 422
354-
assert response.json() == {"detail": "Not a directory"}
356+
assert response.json()["message"] == "Not a directory"
355357

356358

357359
def test_delete_directory_not_empty(project_tmp_path: Path) -> None:
@@ -378,7 +380,7 @@ def test_apply_test_failures(web_sushi_context: Context, mocker: MockerFixture)
378380
mocker.patch.object(web_sushi_context, "_run_plan_tests", side_effect=PlanError("foo"))
379381
response = client.post("/api/commands/apply", json={"environment": "dev"})
380382
assert response.status_code == 422
381-
assert response.json()["detail"] == "foo"
383+
assert response.json()["message"] == "foo"
382384

383385

384386
def test_plan(web_sushi_context: Context) -> None:
@@ -391,10 +393,10 @@ def test_plan(web_sushi_context: Context) -> None:
391393

392394

393395
def test_plan_test_failures(web_sushi_context: Context, mocker: MockerFixture) -> None:
394-
mocker.patch.object(web_sushi_context, "_run_plan_tests", side_effect=PlanError("foo"))
396+
mocker.patch.object(web_sushi_context, "_run_plan_tests", side_effect=PlanError())
395397
response = client.post("/api/plan", json={"environment": "dev"})
396398
assert response.status_code == 422
397-
assert response.json()["detail"] == "foo"
399+
assert response.json()["message"] == "Unable to run a plan"
398400

399401

400402
@pytest.mark.asyncio
@@ -409,7 +411,7 @@ async def test_cancel() -> None:
409411
def test_cancel_no_task() -> None:
410412
response = client.post("/api/plan/cancel")
411413
assert response.status_code == 422
412-
assert response.json() == {"detail": "No active task found."}
414+
assert response.json()["message"] == "Plan/apply is already running"
413415

414416

415417
def test_evaluate(web_sushi_context: Context) -> None:
@@ -460,7 +462,7 @@ def test_render(web_sushi_context: Context) -> None:
460462
def test_render_invalid_model(web_sushi_context: Context) -> None:
461463
response = client.post("/api/commands/render", json={"model": "foo.bar"})
462464
assert response.status_code == 422
463-
assert response.json() == {"detail": "Model not found."}
465+
assert response.json()["message"] == "Unable to find a model"
464466

465467

466468
def test_get_environments(project_context: Context) -> None:

web/client/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
'react/jsx-uses-react': OFF,
1919
'react/react-in-jsx-scope': OFF,
2020
'no-use-before-define': OFF,
21+
'@typescript-eslint/promise-function-async': OFF,
2122
'@typescript-eslint/no-non-null-assertion': OFF,
2223
'@typescript-eslint/no-use-before-define': [
2324
ERROR,

web/client/openapi.json

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
"$ref": "#/components/schemas/Body_apply_api_commands_apply_post"
1515
}
1616
}
17-
},
18-
"required": true
17+
}
1918
},
2019
"responses": {
2120
"200": {
@@ -335,8 +334,7 @@
335334
"$ref": "#/components/schemas/Body_run_plan_api_plan_post"
336335
}
337336
}
338-
},
339-
"required": true
337+
}
340338
},
341339
"responses": {
342340
"200": {
@@ -515,8 +513,13 @@
515513
"application/json": {
516514
"schema": {
517515
"title": "Response Get Models Api Models Get",
518-
"type": "array",
519-
"items": { "$ref": "#/components/schemas/Model" }
516+
"anyOf": [
517+
{
518+
"type": "array",
519+
"items": { "$ref": "#/components/schemas/Model" }
520+
},
521+
{ "$ref": "#/components/schemas/ApiExceptionPayload" }
522+
]
520523
}
521524
}
522525
}
@@ -591,6 +594,26 @@
591594
},
592595
"components": {
593596
"schemas": {
597+
"ApiExceptionPayload": {
598+
"title": "ApiExceptionPayload",
599+
"required": ["timestamp", "status", "message", "origin"],
600+
"type": "object",
601+
"properties": {
602+
"timestamp": { "title": "Timestamp", "type": "integer" },
603+
"status": { "title": "Status", "type": "integer" },
604+
"message": { "title": "Message", "type": "string" },
605+
"origin": { "title": "Origin", "type": "string" },
606+
"trigger": { "title": "Trigger", "type": "string" },
607+
"type": { "title": "Type", "type": "string" },
608+
"description": { "title": "Description", "type": "string" },
609+
"traceback": { "title": "Traceback", "type": "string" },
610+
"stack": {
611+
"title": "Stack",
612+
"type": "array",
613+
"items": { "type": "string" }
614+
}
615+
}
616+
},
594617
"ApplyResponse": {
595618
"title": "ApplyResponse",
596619
"required": ["type"],
@@ -605,7 +628,6 @@
605628
},
606629
"Body_apply_api_commands_apply_post": {
607630
"title": "Body_apply_api_commands_apply_post",
608-
"required": ["environment"],
609631
"type": "object",
610632
"properties": {
611633
"environment": { "title": "Environment", "type": "string" },
@@ -637,7 +659,6 @@
637659
},
638660
"Body_run_plan_api_plan_post": {
639661
"title": "Body_run_plan_api_plan_post",
640-
"required": ["environment"],
641662
"type": "object",
642663
"properties": {
643664
"environment": { "title": "Environment", "type": "string" },
@@ -1001,9 +1022,27 @@
10011022
"kind": { "title": "Kind", "type": "string" },
10021023
"batch_size": { "title": "Batch Size", "type": "integer" },
10031024
"cron": { "title": "Cron", "type": "string" },
1004-
"stamp": { "title": "Stamp", "type": "string" },
1005-
"start": { "title": "Start", "type": "string" },
1006-
"retention": { "title": "Retention", "type": "string" },
1025+
"stamp": {
1026+
"title": "Stamp",
1027+
"anyOf": [
1028+
{ "type": "string", "format": "date" },
1029+
{ "type": "string", "format": "date-time" },
1030+
{ "type": "string" },
1031+
{ "type": "integer" },
1032+
{ "type": "number" }
1033+
]
1034+
},
1035+
"start": {
1036+
"title": "Start",
1037+
"anyOf": [
1038+
{ "type": "string", "format": "date" },
1039+
{ "type": "string", "format": "date-time" },
1040+
{ "type": "string" },
1041+
{ "type": "integer" },
1042+
{ "type": "number" }
1043+
]
1044+
},
1045+
"retention": { "title": "Retention", "type": "integer" },
10071046
"storage_format": { "title": "Storage Format", "type": "string" },
10081047
"time_column": { "title": "Time Column", "type": "string" },
10091048
"tags": { "title": "Tags", "type": "string" },
@@ -1033,6 +1072,20 @@
10331072
"annotated": { "title": "Annotated", "type": "boolean" }
10341073
}
10351074
},
1075+
"ModelKindName": {
1076+
"title": "ModelKindName",
1077+
"enum": [
1078+
"INCREMENTAL_BY_TIME_RANGE",
1079+
"INCREMENTAL_BY_UNIQUE_KEY",
1080+
"FULL",
1081+
"VIEW",
1082+
"EMBEDDED",
1083+
"SEED",
1084+
"EXTERNAL"
1085+
],
1086+
"type": "string",
1087+
"description": "The kind of model, determining how this data is computed and stored in the warehouse."
1088+
},
10361089
"ModelsDiff": {
10371090
"title": "ModelsDiff",
10381091
"type": "object",
@@ -1187,7 +1240,8 @@
11871240
"temp_version": { "title": "Temp Version", "type": "string" },
11881241
"change_category": {
11891242
"$ref": "#/components/schemas/SnapshotChangeCategory"
1190-
}
1243+
},
1244+
"physical_schema": { "title": "Physical Schema", "type": "string" }
11911245
},
11921246
"additionalProperties": false
11931247
},
@@ -1229,8 +1283,7 @@
12291283
"version",
12301284
"physical_schema",
12311285
"parents",
1232-
"is_materialized",
1233-
"is_embedded_kind"
1286+
"kind_name"
12341287
],
12351288
"type": "object",
12361289
"properties": {
@@ -1253,8 +1306,7 @@
12531306
"change_category": {
12541307
"$ref": "#/components/schemas/SnapshotChangeCategory"
12551308
},
1256-
"is_materialized": { "title": "Is Materialized", "type": "boolean" },
1257-
"is_embedded_kind": { "title": "Is Embedded Kind", "type": "boolean" }
1309+
"kind_name": { "$ref": "#/components/schemas/ModelKindName" }
12581310
},
12591311
"additionalProperties": false
12601312
},

web/client/src/api/channels.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
import { isNil } from '../utils'
22

3-
type ChannelCallback = (data: any) => void
3+
type ChannelCallback<TData = any> = (data: TData) => void
44

55
const SSE_CHANNEL = getEventSource('/api/events')
66

77
const CHANNELS = new Map<string, Optional<() => void>>()
88

9-
export function useChannelEvents(): [
10-
(topic: string, callback: ChannelCallback) => Optional<() => void>,
11-
] {
12-
return [
13-
(topic: string, callback: ChannelCallback) => subscribe(topic, callback),
14-
]
9+
export function useChannelEvents(): <TData = any>(
10+
topic: string,
11+
callback: ChannelCallback<TData>,
12+
) => Optional<() => void> {
13+
return (topic, callback) => subscribe(topic, callback)
1514
}
1615

17-
function subscribe(
16+
function subscribe<TData = any>(
1817
topic: string,
19-
callback: ChannelCallback,
18+
callback: ChannelCallback<TData>,
2019
): Optional<() => void> {
2120
if (isNil(topic) || CHANNELS.has(topic)) return CHANNELS.get(topic)
2221

23-
const handler = handleChannelTopicCallback(topic, callback)
22+
const handler = handleChannelTopicCallback<TData>(topic, callback)
2423

2524
SSE_CHANNEL.addEventListener(topic, handler)
2625

@@ -32,11 +31,11 @@ function subscribe(
3231
return CHANNELS.get(topic)
3332
}
3433

35-
function handleChannelTopicCallback(
34+
function handleChannelTopicCallback<TData = any>(
3635
topic: string,
37-
callback: ChannelCallback,
36+
callback: ChannelCallback<TData>,
3837
): (e: MessageEvent) => void {
39-
return (event: MessageEvent) => {
38+
return (event: MessageEvent<string>) => {
4039
if (isNil(topic) || isNil(callback) || isNil(event.data)) return
4140

4241
try {

0 commit comments

Comments
 (0)