Skip to content

Commit 84e2198

Browse files
Add config level change events in ConfigModel (#937)
## Changes * Add config level change events in config model. Whenever a config changes, the corresponding event is fired. * Also, allow writing auth parameters as an override. ## Tests <!-- How is this tested? -->
1 parent 41f076c commit 84e2198

File tree

8 files changed

+131
-64
lines changed

8 files changed

+131
-64
lines changed

packages/databricks-vscode/src/file-managers/BundleWatcher.ts renamed to packages/databricks-vscode/src/bundle/BundleWatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Disposable, EventEmitter, Uri, workspace} from "vscode";
2-
import {BundleFileSet} from "../bundle/BundleFileSet";
2+
import {BundleFileSet} from "./BundleFileSet";
33
import {WithMutex} from "../locking";
44
import path from "path";
55

packages/databricks-vscode/src/bundle/bundleAutocompleteProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {CliWrapper} from "../cli/CliWrapper";
22
import {ExtensionContext, extensions, Uri} from "vscode";
33
import {BundleFileSet} from "./BundleFileSet";
4-
import {BundleWatcher} from "../file-managers/BundleWatcher";
4+
import {BundleWatcher} from "./BundleWatcher";
55

66
export async function registerBundleAutocompleteProvider(
77
cli: CliWrapper,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export {BundleWatcher} from "./BundleWatcher";
2+
export {BundleFileSet, parseBundleYaml, writeBundleYaml} from "./BundleFileSet";
3+
export {registerBundleAutocompleteProvider} from "./bundleAutocompleteProvider";

packages/databricks-vscode/src/configuration/BundleConfigReaderWriter.ts

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import {Uri} from "vscode";
2-
import {
3-
BundleFileSet,
4-
parseBundleYaml,
5-
writeBundleYaml,
6-
} from "../bundle/BundleFileSet";
2+
import {BundleFileSet, parseBundleYaml, writeBundleYaml} from "../bundle";
73
import {BundleTarget} from "../bundle/types";
84
import {Mutex} from "../locking";
9-
import {RemoteUri} from "../sync/SyncDestination";
10-
import {BundleConfigs, isBundleConfig} from "./types";
11-
12-
export class BundleConfigReaderWriter {
5+
import {BundleConfigs, ConfigReaderWriter, isBundleConfig} from "./types";
6+
7+
/**
8+
* Reads and writes bundle configs. This class does not notify when the configs change.
9+
* We use the BundleWatcher to notify when the configs change.
10+
*/
11+
export class BundleConfigReaderWriter
12+
implements ConfigReaderWriter<keyof BundleConfigs>
13+
{
1314
private readonly writeMutex = new Mutex();
1415

1516
private readonly writerMapping: Record<
1617
keyof BundleConfigs,
1718
(t: BundleTarget, v: any) => BundleTarget
1819
> = {
1920
clusterId: this.setClusterId,
20-
authType: this.setAuthType,
21+
authParams: this.setAuthParams,
2122
mode: this.setMode,
2223
host: this.setHost,
2324
workspaceFsPath: this.setWorkspaceFsPath,
@@ -30,7 +31,7 @@ export class BundleConfigReaderWriter {
3031
) => Promise<BundleConfigs[keyof BundleConfigs] | undefined>
3132
> = {
3233
clusterId: this.getClusterId,
33-
authType: this.getAuthType,
34+
authParams: this.getAuthParams,
3435
mode: this.getMode,
3536
host: this.getHost,
3637
workspaceFsPath: this.getWorkspaceFsPath,
@@ -68,9 +69,10 @@ export class BundleConfigReaderWriter {
6869
return target;
6970
}
7071

71-
public async getWorkspaceFsPath(target?: BundleTarget) {
72-
const filePath = target?.workspace?.file_path;
73-
return filePath ? new RemoteUri(filePath) : undefined;
72+
public async getWorkspaceFsPath(
73+
target?: BundleTarget
74+
): Promise<BundleConfigs["workspaceFsPath"]> {
75+
return target?.workspace?.file_path;
7476
}
7577
public setWorkspaceFsPath(
7678
target: BundleTarget,
@@ -80,23 +82,23 @@ export class BundleConfigReaderWriter {
8082
target.workspace = {
8183
...target.workspace,
8284
// eslint-disable-next-line @typescript-eslint/naming-convention
83-
file_path: value?.path,
85+
file_path: value,
8486
};
8587
return target;
8688
}
8789

88-
public async getAuthType(target?: BundleTarget) {
89-
return target?.workspace?.auth_type;
90+
/* eslint-disable @typescript-eslint/no-unused-vars */
91+
92+
public async getAuthParams(target?: BundleTarget) {
93+
return undefined;
9094
}
91-
public setAuthType(target: BundleTarget, value: BundleConfigs["authType"]) {
92-
target = {...target};
93-
target.workspace = {
94-
...target.workspace,
95-
// eslint-disable-next-line @typescript-eslint/naming-convention
96-
auth_type: value,
97-
};
98-
return target;
95+
public setAuthParams(
96+
target: BundleTarget,
97+
value: BundleConfigs["authParams"]
98+
): BundleTarget {
99+
throw new Error("Not implemented");
99100
}
101+
/* eslint-enable @typescript-eslint/no-unused-vars */
100102

101103
get targets() {
102104
return this.bundleFileSet.bundleDataCache.value.then(
@@ -168,19 +170,22 @@ export class BundleConfigReaderWriter {
168170
) {
169171
const file = await this.getFileToWrite(key, target);
170172
if (file === undefined) {
171-
return false;
173+
throw new Error(
174+
`Can't find a file to write property '${key}' of target '${target}'.`
175+
);
172176
}
173177
const data = await parseBundleYaml(file);
174178
const targetData = data.targets?.[target];
175179
if (targetData === undefined) {
176-
return false;
180+
throw new Error(`No target '${target}' for writing '${key}.`);
177181
}
178182

179183
const newTargetData = this.writerMapping[key](targetData, value);
184+
if (JSON.stringify(newTargetData) === JSON.stringify(targetData)) {
185+
return;
186+
}
180187
data.targets = {...data.targets, [target]: newTargetData};
181188
await writeBundleYaml(file, data);
182-
183-
return true;
184189
}
185190

186191
/**

packages/databricks-vscode/src/configuration/ConfigModel.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Disposable, EventEmitter, Uri} from "vscode";
1+
import {Disposable, EventEmitter, Uri, Event} from "vscode";
22
import {
33
BundleConfigs,
44
DatabricksConfigs,
@@ -8,7 +8,7 @@ import {
88
import {ConfigOverrideReaderWriter} from "./ConfigOverrideReaderWriter";
99
import {BundleConfigReaderWriter} from "./BundleConfigReaderWriter";
1010
import {Mutex} from "../locking";
11-
import {BundleWatcher} from "../file-managers/BundleWatcher";
11+
import {BundleWatcher} from "../bundle";
1212
import {CachedValue} from "../locking/CachedValue";
1313
import {StateStorage} from "../vscode-objs/StateStorage";
1414

@@ -38,22 +38,43 @@ export class ConfigModel implements Disposable {
3838
if (this.target === undefined) {
3939
return {};
4040
}
41-
const overrides = this.overrideReaderWriter.readAll(this.target);
41+
const overrides = await this.overrideReaderWriter.readAll(
42+
this.target
43+
);
4244
const bundleConfigs = await this.bundleConfigReaderWriter.readAll(
4345
this.target
4446
);
45-
const newValue = {...bundleConfigs, ...overrides};
46-
47-
if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
48-
this.onDidChangeEmitter.fire();
47+
const newValue: DatabricksConfigs = {
48+
...bundleConfigs,
49+
...overrides,
50+
};
51+
52+
for (const key of <(keyof DatabricksConfigs)[]>(
53+
Object.keys(newValue)
54+
)) {
55+
if (
56+
oldValue === null ||
57+
JSON.stringify(oldValue[key]) !==
58+
JSON.stringify(newValue[key])
59+
) {
60+
this.changeEmitters.get(key)?.emitter.fire();
61+
this.onDidChangeAnyEmitter.fire();
62+
}
4963
}
5064

5165
return newValue;
5266
}
5367
);
5468

55-
private readonly onDidChangeEmitter = new EventEmitter<void>();
56-
public readonly onDidChange = this.onDidChangeEmitter.event;
69+
private readonly changeEmitters = new Map<
70+
keyof DatabricksConfigs | "target",
71+
{
72+
emitter: EventEmitter<void>;
73+
onDidEmit: Event<void>;
74+
}
75+
>();
76+
private onDidChangeAnyEmitter = new EventEmitter<void>();
77+
public onDidChangeAny = this.onDidChangeAnyEmitter.event;
5778

5879
private _target: string | undefined;
5980

@@ -77,7 +98,31 @@ export class ConfigModel implements Disposable {
7798
})
7899
);
79100
}
101+
public async init() {
102+
await this.readTarget();
103+
}
104+
105+
public onDidChange<T extends keyof DatabricksConfigs | "target">(
106+
key: T,
107+
fn: () => any,
108+
thisArgs?: any
109+
) {
110+
if (!this.changeEmitters.has(key)) {
111+
const emitter = new EventEmitter<void>();
112+
this.changeEmitters.set(key, {
113+
emitter: emitter,
114+
onDidEmit: emitter.event,
115+
});
116+
}
80117

118+
const {onDidEmit} = this.changeEmitters.get(key)!;
119+
return onDidEmit(fn, thisArgs);
120+
}
121+
/**
122+
* Try to read target from bundle config.
123+
* If not found, try to read from state storage.
124+
* If not found, try to read the default target from bundle.
125+
*/
81126
public async readTarget() {
82127
const targets = Object.keys(
83128
(await this.bundleConfigReaderWriter.targets) ?? {}
@@ -102,6 +147,9 @@ export class ConfigModel implements Disposable {
102147
return this._target;
103148
}
104149

150+
/**
151+
* Set target in the state storage and invalidate the configs cache.
152+
*/
105153
public async setTarget(target: string | undefined) {
106154
if (target === this._target) {
107155
return;
@@ -112,7 +160,8 @@ export class ConfigModel implements Disposable {
112160
await this.stateStorage.set("databricks.bundle.target", target);
113161
});
114162
await this.configCache.invalidate();
115-
this.onDidChangeEmitter.fire();
163+
this.changeEmitters.get("target")?.emitter.fire();
164+
this.onDidChangeAnyEmitter.fire();
116165
}
117166

118167
public async get<T extends keyof DatabricksConfigs>(
@@ -126,19 +175,22 @@ export class ConfigModel implements Disposable {
126175
key: T,
127176
value?: DatabricksConfigs[T],
128177
handleInteractiveWrite?: (file: Uri | undefined) => any
129-
): Promise<boolean> {
178+
): Promise<void> {
130179
// We work with 1 set of configs throughout the function.
131180
// No changes to the cache can happen when the global mutex is held.
132181
// The assumption is that user doesn't change the target mode in the middle of
133182
// writing a new config.
134183
const {mode} = {...(await this.configCache.value)};
135184

136185
if (this.target === undefined) {
137-
return false;
186+
throw new Error(
187+
`Can't set configuration '${key}' without selecting a target`
188+
);
138189
}
139190
if (isOverrideableConfig(key)) {
140191
return this.overrideReaderWriter.write(key, this.target, value);
141-
} else if (isBundleConfig(key)) {
192+
}
193+
if (isBundleConfig(key)) {
142194
const isInteractive = handleInteractiveWrite !== undefined;
143195

144196
// write to bundle if not interactive and the config can be safely written to bundle
@@ -158,7 +210,6 @@ export class ConfigModel implements Disposable {
158210
handleInteractiveWrite(file);
159211
}
160212
}
161-
return true;
162213
}
163214

164215
dispose() {

packages/databricks-vscode/src/configuration/ConfigOverrideReaderWriter.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {EventEmitter} from "vscode";
22
import {Mutex} from "../locking";
33
import {StateStorage} from "../vscode-objs/StateStorage";
4-
import {OverrideableConfigs} from "./types";
4+
import {OverrideableConfigs, ConfigReaderWriter} from "./types";
55

6-
export class ConfigOverrideReaderWriter {
6+
export class ConfigOverrideReaderWriter
7+
implements ConfigReaderWriter<keyof OverrideableConfigs>
8+
{
79
private writeMutex = new Mutex();
810
private onDidChangeEmitter = new EventEmitter<void>();
911
public readonly onDidChange = this.onDidChangeEmitter.event;
@@ -24,15 +26,16 @@ export class ConfigOverrideReaderWriter {
2426
value?: OverrideableConfigs[T]
2527
) {
2628
const data = this.storage.get("databricks.bundle.overrides");
27-
if (data[target]?.[key] !== undefined) {
28-
if (data[target] === undefined) {
29-
data[target] = {};
30-
}
31-
data[target][key] = value;
32-
await this.storage.set("databricks.bundle.overrides", data);
33-
this.onDidChangeEmitter.fire();
29+
if (data[target] === undefined) {
30+
data[target] = {};
3431
}
35-
return true;
32+
const oldValue = JSON.stringify(data[target][key]);
33+
if (oldValue === JSON.stringify(value)) {
34+
return;
35+
}
36+
data[target][key] = value;
37+
await this.storage.set("databricks.bundle.overrides", data);
38+
this.onDidChangeEmitter.fire();
3639
}
3740

3841
/**

packages/databricks-vscode/src/configuration/types.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
import {RemoteUri} from "../sync/SyncDestination";
2-
31
export type DatabricksConfigs = {
42
host?: string;
53

64
// reconcile with actual mode and auth type enums from bundle
75
mode?: "dev" | "staging" | "prod";
8-
authType?: string;
6+
authParams?: Record<string, any>;
97

108
clusterId?: string;
11-
workspaceFsPath?: RemoteUri;
9+
workspaceFsPath?: string;
1210
};
1311

1412
export const OVERRIDEABLE_CONFIGS = [
1513
"clusterId",
16-
"authType",
14+
"authParams",
1715
"workspaceFsPath",
1816
] as const;
1917

@@ -24,7 +22,7 @@ export type OverrideableConfigs = Pick<
2422

2523
export const BUNDLE_CONFIGS = [
2624
"clusterId",
27-
"authType",
25+
"authParams",
2826
"workspaceFsPath",
2927
"mode",
3028
"host",
@@ -45,3 +43,8 @@ export function isOverrideableConfig(
4543
export function isBundleConfig(key: any): key is keyof BundleConfigs {
4644
return BUNDLE_CONFIGS.includes(key);
4745
}
46+
47+
export interface ConfigReaderWriter<T extends keyof DatabricksConfigs> {
48+
read(key: T, target: string): Promise<DatabricksConfigs[T] | undefined>;
49+
write(key: T, target: string, value?: DatabricksConfigs[T]): Promise<void>;
50+
}

packages/databricks-vscode/src/extension.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
WorkspaceFsCommands,
3333
WorkspaceFsDataProvider,
3434
} from "./workspace-fs";
35-
import {registerBundleAutocompleteProvider} from "./bundle/bundleAutocompleteProvider";
3635
import {CustomWhenContext} from "./vscode-objs/CustomWhenContext";
3736
import {StateStorage} from "./vscode-objs/StateStorage";
3837
import path from "node:path";
@@ -50,8 +49,11 @@ import {DbConnectStatusBarButton} from "./language/DbConnectStatusBarButton";
5049
import {NotebookAccessVerifier} from "./language/notebooks/NotebookAccessVerifier";
5150
import {NotebookInitScriptManager} from "./language/notebooks/NotebookInitScriptManager";
5251
import {showRestartNotebookDialogue} from "./language/notebooks/restartNotebookDialogue";
53-
import {BundleWatcher} from "./file-managers/BundleWatcher";
54-
import {BundleFileSet} from "./bundle/BundleFileSet";
52+
import {
53+
BundleWatcher,
54+
BundleFileSet,
55+
registerBundleAutocompleteProvider,
56+
} from "./bundle";
5557
import {showWhatsNewPopup} from "./whatsNewPopup";
5658

5759
export async function activate(

0 commit comments

Comments
 (0)