Skip to content

Commit 5b8fb23

Browse files
Refactor StateStore to make keys more explicit at the point of use. (#913)
## Changes Advantages of this approach * Adding new variables does not require creating an explicit setter and getter. Default setters and getters should work out of the box. * Easier to see all the information in 1 location. * The keys are immutably exposed to point of use. It allows for potential programatic access to these functions. * Setters require calling the `set` function, which is async. This prevents us from having to create even more functions just to make setting a variable `await`able. ## Tests <!-- How is this tested? -->
1 parent f8fb360 commit 5b8fb23

File tree

9 files changed

+140
-94
lines changed

9 files changed

+140
-94
lines changed

packages/databricks-vscode/src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,10 @@ export async function activate(
566566
);
567567
})
568568
.finally(() => {
569-
stateStorage.lastInstalledExtensionVersion =
570-
packageMetadata.version;
569+
stateStorage.set(
570+
"databricks.lastInstalledExtensionVersion",
571+
packageMetadata.version
572+
);
571573
});
572574

573575
CustomWhenContext.setActivated(true);

packages/databricks-vscode/src/language/ConfigureAutocomplete.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,15 @@ export class ConfigureAutocomplete implements Disposable {
106106
}
107107

108108
async configureCommand() {
109-
this.stateStorage.skipAutocompleteConfigure = false;
109+
this.stateStorage.set("databricks.autocompletion.skipConfigure", false);
110110
return this.configure(true);
111111
}
112112

113113
private async configure(force = false) {
114-
if (!force || this.stateStorage.skipAutocompleteConfigure) {
114+
if (
115+
!force ||
116+
this.stateStorage.get("databricks.autocompletion.skipConfigure")
117+
) {
115118
return;
116119
}
117120

@@ -142,7 +145,10 @@ export class ConfigureAutocomplete implements Disposable {
142145
}
143146

144147
if (choice === "Never for this workspace") {
145-
this.stateStorage.skipAutocompleteConfigure = true;
148+
this.stateStorage.set(
149+
"databricks.autocompletion.skipConfigure",
150+
true
151+
);
146152
return;
147153
}
148154

packages/databricks-vscode/src/language/DbConnectInstallPrompt.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export class DbConnectInstallPrompt implements Disposable {
2121
if (
2222
advertisement &&
2323
executable &&
24-
this.stateStorage.skippedEnvsForDbConnect.includes(executable)
24+
this.stateStorage
25+
.get("databricks.debugging.skipDbConnectInstallForEnvs")
26+
.includes(executable)
2527
) {
2628
return;
2729
}
@@ -82,7 +84,10 @@ export class DbConnectInstallPrompt implements Disposable {
8284

8385
case "Never for this environment":
8486
if (executable) {
85-
this.stateStorage.skipDbConnectInstallForEnv(executable);
87+
this.stateStorage.set(
88+
"databricks.debugging.skipDbConnectInstallForEnvs",
89+
[executable]
90+
);
8691
}
8792
break;
8893

packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ export class MsPythonExtensionWrapper implements Disposable {
4545
if (this._terminal) {
4646
return this._terminal;
4747
}
48-
const terminalName = `databricks-pip-${this.stateStorage.fixedUUID.slice(
49-
0,
50-
8
51-
)}`;
48+
const terminalName = `databricks-pip-${this.stateStorage
49+
.get("databricks.fixedUUID")
50+
.slice(0, 8)}`;
5251

5352
this._terminal = window.createTerminal({
5453
name: terminalName,

packages/databricks-vscode/src/language/notebooks/NotebookAccessVerifier.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ export class NotebookAccessVerifier extends MultiStepAccessVerifier {
6060
"latest"
6161
);
6262
return true;
63-
break;
6463

6564
case "Change environment":
6665
await this.pythonExtension.selectPythonInterpreter();
Lines changed: 111 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,100 @@
11
import {randomUUID} from "crypto";
22
import {ExtensionContext} from "vscode";
33

4-
export class StateStorage {
5-
constructor(private context: ExtensionContext) {}
4+
/* eslint-disable @typescript-eslint/naming-convention */
5+
type KeyInfo<V> = {
6+
location: "global" | "workspace";
7+
defaultValue?: V;
8+
getter?: (storage: StateStorage, value: V | undefined) => V | undefined;
9+
setter?: (storage: StateStorage, value: V | undefined) => V | undefined;
10+
};
611

7-
get fixedRandom() {
8-
let randomNum = this.context.globalState.get<number>(
9-
"databricks.fixedRandom"
10-
);
11-
if (!randomNum) {
12-
randomNum = Math.random();
13-
this.context.globalState.update(
14-
"databricks.fixedRandom",
15-
randomNum
16-
);
17-
}
18-
return randomNum;
19-
}
12+
function withType<V>() {
13+
return function <D extends KeyInfo<V>>(data: D) {
14+
return data as typeof data & {_type: V};
15+
};
16+
}
2017

21-
get wsfsFeatureFlag() {
22-
return true;
23-
}
18+
const Keys = {
19+
"databricks.clusterId": withType<string>()({
20+
location: "workspace",
21+
}),
2422

25-
get skipSwitchToWorkspace() {
26-
return this.context.workspaceState.get(
27-
"databricks.wsfs.skipSwitchToWorkspace",
28-
false
29-
);
30-
}
23+
"databricks.wsfs.skipSwitchToWorkspace": withType<boolean>()({
24+
location: "workspace",
25+
defaultValue: false,
26+
}),
3127

32-
set skipSwitchToWorkspace(value: boolean) {
33-
this.context.workspaceState.update(
34-
"databricks.wsfs.skipSwitchToWorkspace",
35-
value
36-
);
37-
}
28+
"databricks.autocompletion.skipConfigure": withType<boolean>()({
29+
location: "workspace",
30+
defaultValue: false,
31+
}),
3832

39-
get skipAutocompleteConfigure() {
40-
return this.context.workspaceState.get(
41-
"databricks.autocompletion.skipConfigure",
42-
false
43-
);
44-
}
33+
"databricks.fixedRandom": withType<number>()({
34+
location: "global",
35+
getter: (storage, value) => {
36+
if (value === undefined) {
37+
value = Math.random();
38+
storage.set("databricks.fixedRandom", value);
39+
}
40+
return value;
41+
},
42+
}),
4543

46-
set skipAutocompleteConfigure(value: boolean) {
47-
this.context.workspaceState.update(
48-
"databricks.autocompletion.skipConfigure",
49-
value
50-
);
51-
}
44+
"databricks.fixedUUID": withType<string>()({
45+
location: "workspace",
46+
getter: (storage, value) => {
47+
if (value === undefined) {
48+
value = randomUUID();
49+
storage.set("databricks.fixedUUID", value);
50+
}
51+
return value;
52+
},
53+
}),
5254

53-
get skippedEnvsForDbConnect() {
54-
return this.context.globalState.get<string[]>(
55-
"databricks.debugging.skipDbConnectInstallForEnvs",
56-
[]
57-
);
58-
}
55+
"databricks.debugging.skipDbConnectInstallForEnvs": withType<string[]>()({
56+
location: "global",
57+
defaultValue: [],
58+
setter: (storage, value) => {
59+
if (value === undefined || value.length === 0) {
60+
return undefined;
61+
}
62+
const currentEnvs: string[] = storage.get(
63+
"databricks.debugging.skipDbConnectInstallForEnvs"
64+
);
65+
if (!currentEnvs.includes(value[0])) {
66+
currentEnvs.push(value[0]);
67+
}
68+
return currentEnvs;
69+
},
70+
}),
5971

60-
skipDbConnectInstallForEnv(value: string) {
61-
const currentEnvs = this.skippedEnvsForDbConnect;
62-
if (!currentEnvs.includes(value)) {
63-
currentEnvs.push(value);
64-
}
65-
this.context.globalState.update(
66-
"databricks.debugging.skipDbConnectInstallForEnvs",
67-
currentEnvs
68-
);
69-
}
72+
"databricks.lastInstalledExtensionVersion": withType<string>()({
73+
location: "workspace",
74+
defaultValue: "0.0.0",
75+
}),
76+
};
77+
78+
type ValueType<K extends keyof typeof Keys> = (typeof Keys)[K]["_type"];
79+
type GetterReturnType<D extends KeyInfo<any>> = D extends {getter: infer G}
80+
? G extends (...args: any[]) => any
81+
? ReturnType<G>
82+
: undefined
83+
: undefined;
84+
85+
type DefaultValue<K extends keyof typeof Keys> =
86+
"defaultValue" extends keyof (typeof Keys)[K]
87+
? ValueType<K>
88+
: GetterReturnType<(typeof Keys)[K]>;
89+
90+
type KeyInfoWithType<V> = KeyInfo<V> & {
91+
_type: V;
92+
_getterType: V | never | undefined;
93+
};
94+
/* eslint-enable @typescript-eslint/naming-convention */
7095

96+
export class StateStorage {
97+
constructor(private context: ExtensionContext) {}
7198
get skippedEnvsForDatabricksSdk() {
7299
return this.context.globalState.get<string[]>(
73100
"databricks.debugging.skipDatabricksSdkInstallForEnvs",
@@ -85,29 +112,35 @@ export class StateStorage {
85112
currentEnvs
86113
);
87114
}
88-
89-
get lastInstalledExtensionVersion() {
90-
return this.context.workspaceState.get<string>(
91-
"databricks.lastInstalledExtensionVersion",
92-
"0.0.0"
93-
);
115+
private getStateObject(location: "global" | "workspace") {
116+
switch (location) {
117+
case "workspace":
118+
return this.context.workspaceState;
119+
case "global":
120+
return this.context.globalState;
121+
}
94122
}
95123

96-
set lastInstalledExtensionVersion(value: string) {
97-
this.context.workspaceState.update(
98-
"databricks.lastInstalledExtensionVersion",
99-
value
100-
);
124+
get<K extends keyof typeof Keys>(key: K): ValueType<K> | DefaultValue<K> {
125+
const details = Keys[key] as KeyInfoWithType<ValueType<K>>;
126+
127+
const value =
128+
this.getStateObject(details.location).get<ValueType<K>>(key) ??
129+
details.defaultValue;
130+
131+
return (
132+
details.getter !== undefined ? details.getter(this, value) : value
133+
) as ValueType<K> | DefaultValue<K>;
101134
}
102135

103-
get fixedUUID() {
104-
let uuid = this.context.workspaceState.get<string>(
105-
"databricks.fixedUUID"
106-
);
107-
if (!uuid) {
108-
uuid = randomUUID();
109-
this.context.workspaceState.update("databricks.fixedUUID", uuid);
110-
}
111-
return uuid;
136+
async set<K extends keyof typeof Keys>(
137+
key: K,
138+
value: ValueType<K> | undefined
139+
) {
140+
const details = Keys[key] as KeyInfoWithType<ValueType<K>>;
141+
value =
142+
details.setter !== undefined ? details.setter(this, value) : value;
143+
await this.getStateObject(details.location).update(key, value);
144+
return;
112145
}
113146
}

packages/databricks-vscode/src/whatsNewPopup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export async function showWhatsNewPopup(
4444
}
4545

4646
const previousVersion =
47-
semver.parse(storage.lastInstalledExtensionVersion) ??
47+
semver.parse(storage.get("databricks.lastInstalledExtensionVersion")) ??
4848
new semver.SemVer("0.0.0");
4949

5050
// if the extension is downgraded, we do not want to show the popup

packages/databricks-vscode/src/workspace-fs/WorkspaceFsAccessVerifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export async function switchToWorkspacePrompt(
4343
});
4444

4545
if (selection === "Don't show again") {
46-
stateStorage.skipSwitchToWorkspace = true;
46+
stateStorage.set("databricks.wsfs.skipSwitchToWorkspace", true);
4747
return;
4848
}
4949

@@ -142,7 +142,7 @@ export class WorkspaceFsAccessVerifier implements Disposable {
142142
if (
143143
workspaceConfigs.enableFilesInWorkspace ||
144144
!(await this.isEnabledForWorkspace()) ||
145-
this.stateStorage.skipSwitchToWorkspace
145+
this.stateStorage.get("databricks.wsfs.skipSwitchToWorkspace")
146146
) {
147147
return;
148148
}

packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export class WorkspaceFsCommands implements Disposable {
4444
const element = await root?.mkdir(
4545
`${path.basename(
4646
this.workspaceFolder.fsPath
47-
)}-${this.stateStorage.fixedUUID.slice(0, 8)}`
47+
)}-${this.stateStorage
48+
.get("databricks.fixedUUID")
49+
.slice(0, 8)}`
4850
);
4951
if (element) {
5052
await this.connectionManager.attachSyncDestination(

0 commit comments

Comments
 (0)