Skip to content

Commit 9a32556

Browse files
Return config source from ConfigModel (#941)
## Changes * We need config source to display the overrides clearly in the UI. This PR allows config source to be passed through `ConfigModel`. ## Tests Manual
1 parent 84e2198 commit 9a32556

File tree

5 files changed

+147
-93
lines changed

5 files changed

+147
-93
lines changed

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

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import {Uri} from "vscode";
22
import {BundleFileSet, parseBundleYaml, writeBundleYaml} from "../bundle";
33
import {BundleTarget} from "../bundle/types";
44
import {Mutex} from "../locking";
5-
import {BundleConfigs, ConfigReaderWriter, isBundleConfig} from "./types";
5+
import {BundleConfig, ConfigReaderWriter, isBundleConfigKey} from "./types";
66

77
/**
88
* Reads and writes bundle configs. This class does not notify when the configs change.
99
* We use the BundleWatcher to notify when the configs change.
1010
*/
1111
export class BundleConfigReaderWriter
12-
implements ConfigReaderWriter<keyof BundleConfigs>
12+
implements ConfigReaderWriter<keyof BundleConfig>
1313
{
1414
private readonly writeMutex = new Mutex();
1515

1616
private readonly writerMapping: Record<
17-
keyof BundleConfigs,
17+
keyof BundleConfig,
1818
(t: BundleTarget, v: any) => BundleTarget
1919
> = {
2020
clusterId: this.setClusterId,
@@ -25,10 +25,10 @@ export class BundleConfigReaderWriter
2525
};
2626

2727
private readonly readerMapping: Record<
28-
keyof BundleConfigs,
28+
keyof BundleConfig,
2929
(
3030
t?: BundleTarget
31-
) => Promise<BundleConfigs[keyof BundleConfigs] | undefined>
31+
) => Promise<BundleConfig[keyof BundleConfig] | undefined>
3232
> = {
3333
clusterId: this.getClusterId,
3434
authParams: this.getAuthParams,
@@ -42,7 +42,7 @@ export class BundleConfigReaderWriter
4242
public async getHost(target?: BundleTarget) {
4343
return target?.workspace?.host;
4444
}
45-
public setHost(target: BundleTarget, value: BundleConfigs["host"]) {
45+
public setHost(target: BundleTarget, value: BundleConfig["host"]) {
4646
target = {...target}; // create an explicit copy so as to not modify the original object
4747
target.workspace = {...target.workspace, host: value};
4848
return target;
@@ -51,7 +51,7 @@ export class BundleConfigReaderWriter
5151
public async getMode(target?: BundleTarget) {
5252
return target?.mode;
5353
}
54-
public setMode(target: BundleTarget, value: BundleConfigs["mode"]) {
54+
public setMode(target: BundleTarget, value: BundleConfig["mode"]) {
5555
target = {...target};
5656
target.mode = value;
5757
return target;
@@ -62,7 +62,7 @@ export class BundleConfigReaderWriter
6262
}
6363
public setClusterId(
6464
target: BundleTarget,
65-
value: BundleConfigs["clusterId"]
65+
value: BundleConfig["clusterId"]
6666
) {
6767
target = {...target};
6868
target.compute_id = value;
@@ -71,12 +71,12 @@ export class BundleConfigReaderWriter
7171

7272
public async getWorkspaceFsPath(
7373
target?: BundleTarget
74-
): Promise<BundleConfigs["workspaceFsPath"]> {
74+
): Promise<BundleConfig["workspaceFsPath"]> {
7575
return target?.workspace?.file_path;
7676
}
7777
public setWorkspaceFsPath(
7878
target: BundleTarget,
79-
value: BundleConfigs["workspaceFsPath"]
79+
value: BundleConfig["workspaceFsPath"]
8080
) {
8181
target = {...target};
8282
target.workspace = {
@@ -94,7 +94,7 @@ export class BundleConfigReaderWriter
9494
}
9595
public setAuthParams(
9696
target: BundleTarget,
97-
value: BundleConfigs["authParams"]
97+
value: BundleConfig["authParams"]
9898
): BundleTarget {
9999
throw new Error("Not implemented");
100100
}
@@ -123,10 +123,7 @@ export class BundleConfigReaderWriter
123123
});
124124
}
125125

126-
async getFileToWrite<T extends keyof BundleConfigs>(
127-
key: T,
128-
target: string
129-
) {
126+
async getFileToWrite<T extends keyof BundleConfig>(key: T, target: string) {
130127
const priorityList: {uri: Uri; priority: number}[] = [];
131128
await this.bundleFileSet.forEach(async (data, file) => {
132129
// try to find a file which has the config
@@ -163,10 +160,10 @@ export class BundleConfigReaderWriter
163160
* @returns status of the write
164161
*/
165162
@Mutex.synchronise("writeMutex")
166-
async write<T extends keyof BundleConfigs>(
163+
async write<T extends keyof BundleConfig>(
167164
key: T,
168165
target: string,
169-
value?: BundleConfigs[T]
166+
value?: BundleConfig[T]
170167
) {
171168
const file = await this.getFileToWrite(key, target);
172169
if (file === undefined) {
@@ -194,22 +191,22 @@ export class BundleConfigReaderWriter
194191
* @param target target to read from
195192
* @returns value of the config
196193
*/
197-
async read<T extends keyof BundleConfigs>(key: T, target: string) {
194+
async read<T extends keyof BundleConfig>(key: T, target: string) {
198195
const targetObject = (await this.bundleFileSet.bundleDataCache.value)
199196
.targets?.[target];
200197
return (await this.readerMapping[key](targetObject)) as
201-
| BundleConfigs[T]
198+
| BundleConfig[T]
202199
| undefined;
203200
}
204201

205202
async readAll(target: string) {
206203
const configs = {} as any;
207204
for (const key of Object.keys(this.readerMapping)) {
208-
if (!isBundleConfig(key)) {
205+
if (!isBundleConfigKey(key)) {
209206
continue;
210207
}
211208
configs[key] = await this.read(key, target);
212209
}
213-
return configs as BundleConfigs;
210+
return configs as BundleConfig;
214211
}
215212
}

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

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {Disposable, EventEmitter, Uri, Event} from "vscode";
22
import {
3-
BundleConfigs,
4-
DatabricksConfigs,
5-
isBundleConfig,
6-
isOverrideableConfig,
3+
BundleConfig,
4+
DATABRICKS_CONFIG_KEYS,
5+
DatabricksConfigSource,
6+
DatabricksConfig,
7+
isBundleConfigKey,
8+
isOverrideableConfigKey,
79
} from "./types";
810
import {ConfigOverrideReaderWriter} from "./ConfigOverrideReaderWriter";
911
import {BundleConfigReaderWriter} from "./BundleConfigReaderWriter";
@@ -13,61 +15,82 @@ import {CachedValue} from "../locking/CachedValue";
1315
import {StateStorage} from "../vscode-objs/StateStorage";
1416

1517
function isDirectToBundleConfig(
16-
key: keyof BundleConfigs,
17-
mode?: BundleConfigs["mode"]
18+
key: keyof BundleConfig,
19+
mode?: BundleConfig["mode"]
1820
) {
19-
const directToBundleConfigs: (keyof BundleConfigs)[] = [];
21+
const directToBundleConfigs: (keyof BundleConfig)[] = [];
2022
if (mode !== undefined) {
2123
// filter by mode
2224
}
2325
return directToBundleConfigs.includes(key);
2426
}
2527

26-
const defaults: DatabricksConfigs = {
28+
const defaults: DatabricksConfig = {
2729
mode: "dev",
2830
};
31+
2932
/**
3033
* In memory view of the databricks configs loaded from overrides and bundle.
3134
*/
3235
export class ConfigModel implements Disposable {
3336
private disposables: Disposable[] = [];
3437

3538
private readonly configsMutex = new Mutex();
36-
private readonly configCache = new CachedValue<DatabricksConfigs>(
37-
async (oldValue) => {
38-
if (this.target === undefined) {
39-
return {};
40-
}
41-
const overrides = await this.overrideReaderWriter.readAll(
42-
this.target
43-
);
44-
const bundleConfigs = await this.bundleConfigReaderWriter.readAll(
45-
this.target
46-
);
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-
}
39+
private readonly configCache = new CachedValue<{
40+
config: DatabricksConfig;
41+
source: DatabricksConfigSource;
42+
}>(async (oldValue) => {
43+
if (this.target === undefined) {
44+
return {config: {}, source: {}};
45+
}
46+
const overrides = await this.overrideReaderWriter.readAll(this.target);
47+
const bundleConfigs = await this.bundleConfigReaderWriter.readAll(
48+
this.target
49+
);
50+
const newValue: DatabricksConfig = {
51+
...bundleConfigs,
52+
...overrides,
53+
};
54+
55+
const source: DatabricksConfigSource = {};
56+
57+
/* By default undefined values are considered to have come from bundle.
58+
This is because when override for a key is undefined, it means that the key
59+
is not overridden and we want to get the value from bundle.
60+
*/
61+
DATABRICKS_CONFIG_KEYS.forEach((key) => {
62+
source[key] =
63+
overrides !== undefined && key in overrides
64+
? "override"
65+
: "bundle";
66+
});
67+
68+
let didAnyConfigChange = false;
69+
for (const key of DATABRICKS_CONFIG_KEYS) {
70+
if (
71+
// Old value is null, but new value has the key
72+
(oldValue === null && newValue[key] !== undefined) ||
73+
// Old value is not null, and old and new values for the key are different
74+
(oldValue !== null &&
75+
JSON.stringify(oldValue.config[key]) !==
76+
JSON.stringify(newValue[key]))
77+
) {
78+
this.changeEmitters.get(key)?.emitter.fire();
79+
didAnyConfigChange = true;
6380
}
81+
}
6482

65-
return newValue;
83+
if (didAnyConfigChange) {
84+
this.onDidChangeAnyEmitter.fire();
6685
}
67-
);
86+
return {
87+
config: newValue,
88+
source: source,
89+
};
90+
});
6891

6992
private readonly changeEmitters = new Map<
70-
keyof DatabricksConfigs | "target",
93+
keyof DatabricksConfig | "target",
7194
{
7295
emitter: EventEmitter<void>;
7396
onDidEmit: Event<void>;
@@ -102,7 +125,7 @@ export class ConfigModel implements Disposable {
102125
await this.readTarget();
103126
}
104127

105-
public onDidChange<T extends keyof DatabricksConfigs | "target">(
128+
public onDidChange<T extends keyof DatabricksConfig | "target">(
106129
key: T,
107130
fn: () => any,
108131
thisArgs?: any
@@ -164,33 +187,59 @@ export class ConfigModel implements Disposable {
164187
this.onDidChangeAnyEmitter.fire();
165188
}
166189

167-
public async get<T extends keyof DatabricksConfigs>(
190+
public async get<T extends keyof DatabricksConfig>(
191+
key: T
192+
): Promise<DatabricksConfig[T] | undefined> {
193+
return (await this.configCache.value).config[key] ?? defaults[key];
194+
}
195+
196+
/**
197+
* Return config value along with source of the config.
198+
* Refer to {@link DatabricksConfigSource} for possible values.
199+
*/
200+
public async getS<T extends keyof DatabricksConfig>(
168201
key: T
169-
): Promise<DatabricksConfigs[T] | undefined> {
170-
return (await this.configCache.value)[key] ?? defaults[key];
202+
): Promise<
203+
| {
204+
config: DatabricksConfig[T];
205+
source: DatabricksConfigSource[T] | "default";
206+
}
207+
| undefined
208+
> {
209+
const {config: fullConfig, source: fullSource} = await this.configCache
210+
.value;
211+
const config = fullConfig[key] ?? defaults[key];
212+
const source =
213+
fullConfig[key] !== undefined ? fullSource[key] : "default";
214+
return config
215+
? {
216+
config,
217+
source,
218+
}
219+
: undefined;
171220
}
172221

173222
@Mutex.synchronise("configsMutex")
174-
public async set<T extends keyof DatabricksConfigs>(
223+
public async set<T extends keyof DatabricksConfig>(
175224
key: T,
176-
value?: DatabricksConfigs[T],
225+
value?: DatabricksConfig[T],
177226
handleInteractiveWrite?: (file: Uri | undefined) => any
178227
): Promise<void> {
179228
// We work with 1 set of configs throughout the function.
180229
// No changes to the cache can happen when the global mutex is held.
181230
// The assumption is that user doesn't change the target mode in the middle of
182231
// writing a new config.
183-
const {mode} = {...(await this.configCache.value)};
232+
const {mode} = {...(await this.configCache.value).config};
184233

185234
if (this.target === undefined) {
186235
throw new Error(
187236
`Can't set configuration '${key}' without selecting a target`
188237
);
189238
}
190-
if (isOverrideableConfig(key)) {
239+
if (isOverrideableConfigKey(key)) {
191240
return this.overrideReaderWriter.write(key, this.target, value);
192241
}
193-
if (isBundleConfig(key)) {
242+
if (isBundleConfigKey(key)) {
194243
const isInteractive = handleInteractiveWrite !== undefined;
195244

196245
// write to bundle if not interactive and the config can be safely written to bundle

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

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

66
export class ConfigOverrideReaderWriter
7-
implements ConfigReaderWriter<keyof OverrideableConfigs>
7+
implements ConfigReaderWriter<keyof OverrideableConfig>
88
{
99
private writeMutex = new Mutex();
1010
private onDidChangeEmitter = new EventEmitter<void>();
@@ -20,10 +20,10 @@ export class ConfigOverrideReaderWriter
2020
* @returns status of the write
2121
*/
2222
@Mutex.synchronise("writeMutex")
23-
async write<T extends keyof OverrideableConfigs>(
23+
async write<T extends keyof OverrideableConfig>(
2424
key: T,
2525
target: string,
26-
value?: OverrideableConfigs[T]
26+
value?: OverrideableConfig[T]
2727
) {
2828
const data = this.storage.get("databricks.bundle.overrides");
2929
if (data[target] === undefined) {
@@ -49,10 +49,10 @@ export class ConfigOverrideReaderWriter
4949
return this.storage.get("databricks.bundle.overrides")[target];
5050
}
5151

52-
async read<T extends keyof OverrideableConfigs>(
52+
async read<T extends keyof OverrideableConfig>(
5353
key: T,
5454
target: string
55-
): Promise<OverrideableConfigs[T] | undefined> {
55+
): Promise<OverrideableConfig[T] | undefined> {
5656
return this.storage.get("databricks.bundle.overrides")[target]?.[key];
5757
}
5858
}

0 commit comments

Comments
 (0)