Skip to content

Commit b7cbe4c

Browse files
authored
Add partial update to pipelines (#1453)
## Changes I've changed the "normal" pipeline run action to use `run-all` icon, and the partial update run uses the `run` instead. Currently we don't pre-load pipeline updates, so if a user hasn't executed a pipeline from the extension, the partial update action will not show a list of tables to select from (and users will have to type the table names manually). After the pipeline is executed we show the tables selection in the partial update action. Pre-loading pipeline updates is for later. New action: <img width="676" alt="Screenshot 2024-11-18 at 15 31 07" src="https://github.com/user-attachments/assets/31022043-9820-497a-a5bf-8d2b71fed24a"> The case when we don't know about past events: <img width="896" alt="Screenshot 2024-11-15 at 11 24 52" src="https://github.com/user-attachments/assets/6bdf0f49-399b-4657-a2d1-8207d4e8f78c"> <img width="896" alt="Screenshot 2024-11-15 at 11 25 01" src="https://github.com/user-attachments/assets/3044ddf2-1059-4775-aa00-9a28165c1e77"> The case when we offer a selection of table names based on past runs: <img width="896" alt="Screenshot 2024-11-15 at 11 26 28" src="https://github.com/user-attachments/assets/e4f834c3-918a-4f0a-92ab-182305fe450c"> <img width="896" alt="Screenshot 2024-11-15 at 11 25 13" src="https://github.com/user-attachments/assets/48c5a21f-7423-4585-99f9-62a682101ece"> ## Tests Partially covered by unit tests
1 parent d410b62 commit b7cbe4c

File tree

9 files changed

+439
-24
lines changed

9 files changed

+439
-24
lines changed

packages/databricks-vscode/package.json

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,28 @@
218218
"category": "Databricks"
219219
},
220220
{
221-
"command": "databricks.bundle.deployAndRun",
222-
"icon": "$(debug-start)",
223-
"title": "Deploy the bundle and run the resource",
221+
"command": "databricks.bundle.deployAndRunJob",
222+
"icon": "$(run)",
223+
"title": "Deploy the bundle and run the workflow",
224+
"enablement": "databricks.context.activated && databricks.context.bundle.isTargetSet && databricks.context.bundle.deploymentState == idle",
225+
"category": "Databricks"
226+
},
227+
{
228+
"command": "databricks.bundle.deployAndRunPipeline",
229+
"icon": "$(run-all)",
230+
"title": "Deploy the bundle and run the pipeline",
231+
"enablement": "databricks.context.activated && databricks.context.bundle.isTargetSet && databricks.context.bundle.deploymentState == idle",
232+
"category": "Databricks"
233+
},
234+
{
235+
"command": "databricks.bundle.deployAndRunSelectedTables",
236+
"icon": "$(run)",
237+
"title": "Deploy the bundle and select pipeline tables for partial update",
224238
"enablement": "databricks.context.activated && databricks.context.bundle.isTargetSet && databricks.context.bundle.deploymentState == idle",
225239
"category": "Databricks"
226240
},
227241
{
228-
"command": "databricks.bundle.deployAndValidate",
242+
"command": "databricks.bundle.deployAndValidatePipeline",
229243
"icon": {
230244
"dark": "resources/dark/check-line-icon.svg",
231245
"light": "resources/light/check-line-icon.svg"
@@ -550,12 +564,22 @@
550564
"group": "inline@0"
551565
},
552566
{
553-
"command": "databricks.bundle.deployAndRun",
554-
"when": "view == dabsResourceExplorerView && viewItem =~ /^databricks.bundle.*.runnable.*$/ && databricks.context.bundle.deploymentState == idle",
567+
"command": "databricks.bundle.deployAndRunJob",
568+
"when": "view == dabsResourceExplorerView && viewItem =~ /^databricks.bundle.resource=jobs.runnable.*$/ && databricks.context.bundle.deploymentState == idle",
555569
"group": "inline@0"
556570
},
557571
{
558-
"command": "databricks.bundle.deployAndValidate",
572+
"command": "databricks.bundle.deployAndRunPipeline",
573+
"when": "view == dabsResourceExplorerView && viewItem =~ /^databricks.bundle.resource=pipelines.runnable.*$/ && databricks.context.bundle.deploymentState == idle",
574+
"group": "inline@0"
575+
},
576+
{
577+
"command": "databricks.bundle.deployAndValidatePipeline",
578+
"when": "view == dabsResourceExplorerView && viewItem =~ /^databricks.bundle.resource=pipelines.runnable.*$/ && databricks.context.bundle.deploymentState == idle",
579+
"group": "inline@0"
580+
},
581+
{
582+
"command": "databricks.bundle.deployAndRunSelectedTables",
559583
"when": "view == dabsResourceExplorerView && viewItem =~ /^databricks.bundle.resource=pipelines.runnable.*$/ && databricks.context.bundle.deploymentState == idle",
560584
"group": "inline@0"
561585
},
@@ -657,11 +681,19 @@
657681
"when": "false"
658682
},
659683
{
660-
"command": "databricks.bundle.deployAndRun",
684+
"command": "databricks.bundle.deployAndRunJob",
685+
"when": "false"
686+
},
687+
{
688+
"command": "databricks.bundle.deployAndRunPipeline",
689+
"when": "false"
690+
},
691+
{
692+
"command": "databricks.bundle.deployAndValidatePipeline",
661693
"when": "false"
662694
},
663695
{
664-
"command": "databricks.bundle.deployAndValidate",
696+
"command": "databricks.bundle.deployAndRunSelectedTables",
665697
"when": "false"
666698
},
667699
{
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {BundlePipelinesManager} from "./BundlePipelinesManager";
2+
import {BundleRunStatusManager} from "./run/BundleRunStatusManager";
3+
import {ConfigModel} from "../configuration/models/ConfigModel";
4+
import {mock, instance, when} from "ts-mockito";
5+
import assert from "assert";
6+
import {EventEmitter} from "vscode";
7+
import {install, InstalledClock} from "@sinonjs/fake-timers";
8+
9+
describe(__filename, () => {
10+
let runStatusManager: BundleRunStatusManager;
11+
let configModel: ConfigModel;
12+
let manager: BundlePipelinesManager;
13+
let eventEmitter: EventEmitter<void>;
14+
let clock: InstalledClock;
15+
16+
beforeEach(() => {
17+
clock = install();
18+
eventEmitter = new EventEmitter();
19+
runStatusManager = mock<BundleRunStatusManager>();
20+
configModel = mock<ConfigModel>();
21+
when(runStatusManager.onDidChange).thenReturn(eventEmitter.event);
22+
when(configModel.onDidChangeKey("remoteStateConfig")).thenReturn(
23+
new EventEmitter<void>().event
24+
);
25+
when(configModel.onDidChangeTarget).thenReturn(
26+
new EventEmitter<void>().event
27+
);
28+
manager = new BundlePipelinesManager(
29+
instance(runStatusManager),
30+
instance(configModel)
31+
);
32+
});
33+
34+
afterEach(() => {
35+
clock.uninstall();
36+
});
37+
38+
it("should update pipeline datasets from run events", async () => {
39+
let datasets;
40+
const remoteState = {resources: {pipelines: {pipeline1: {}}}};
41+
when(configModel.get("remoteStateConfig")).thenResolve(remoteState);
42+
const runStatuses = new Map();
43+
when(runStatusManager.runStatuses).thenReturn(runStatuses);
44+
45+
/* eslint-disable @typescript-eslint/naming-convention */
46+
const firstRun = {
47+
data: {
48+
update: {creation_time: 10},
49+
},
50+
events: [
51+
{origin: {dataset_name: "table1"}},
52+
{origin: {not_a_dataset_name: "table1.5"}},
53+
{origin: {dataset_name: "table2"}},
54+
],
55+
};
56+
/* eslint-enable @typescript-eslint/naming-convention */
57+
runStatuses.set("pipelines.pipeline1", firstRun);
58+
59+
eventEmitter.fire();
60+
await clock.runToLastAsync();
61+
62+
datasets = manager.getDatasets("pipeline1");
63+
assert.strictEqual(datasets.size, 2);
64+
assert(datasets.has("table1"));
65+
assert(datasets.has("table2"));
66+
67+
/* eslint-disable @typescript-eslint/naming-convention */
68+
const secondPartialRun = {
69+
data: {
70+
update: {
71+
creation_time: 100,
72+
refresh_selection: ["table3", "table4"],
73+
},
74+
},
75+
events: [
76+
{origin: {dataset_name: "table3"}},
77+
{origin: {not_a_dataset_name: "table3.5"}},
78+
{origin: {dataset_name: "table4"}},
79+
],
80+
};
81+
/* eslint-enable @typescript-eslint/naming-convention */
82+
83+
runStatuses.set("pipelines.pipeline1", secondPartialRun);
84+
eventEmitter.fire();
85+
await clock.runToLastAsync();
86+
87+
datasets = manager.getDatasets("pipeline1");
88+
assert.strictEqual(datasets.size, 4);
89+
assert(datasets.has("table1"));
90+
assert(datasets.has("table2"));
91+
assert(datasets.has("table3"));
92+
assert(datasets.has("table4"));
93+
94+
/* eslint-disable @typescript-eslint/naming-convention */
95+
const finalFullRefreshRun = {
96+
data: {
97+
update: {
98+
creation_time: 200,
99+
refresh_selection: [],
100+
},
101+
},
102+
events: [
103+
{origin: {dataset_name: "table_new"}},
104+
{origin: {not_a_dataset_name: "not a table"}},
105+
{origin: {dataset_name: "table_final"}},
106+
],
107+
};
108+
/* eslint-enable @typescript-eslint/naming-convention */
109+
runStatuses.set("pipelines.pipeline1", finalFullRefreshRun);
110+
eventEmitter.fire();
111+
await clock.runToLastAsync();
112+
113+
// Only the datasets from the final full-refresh run should be left
114+
datasets = manager.getDatasets("pipeline1");
115+
assert.strictEqual(datasets.size, 2);
116+
assert(datasets.has("table_new"));
117+
assert(datasets.has("table_final"));
118+
});
119+
});

0 commit comments

Comments
 (0)