Skip to content

Commit fa4013c

Browse files
Update login wizard and show cancel button for running resources (#1071)
* Login wizard * In host selection menu, don't show same host multiple times if there are multiple profiles. Only show it once with all the profiles listed in the description. * Show existing profiles first in menu for creating/selecting auth type. * Dabs Explorer * Show the name of resource in the tooltip for created/deleted resources * Show the cancel button for running resources (removed due to a previous PR). * Actually show the project init icon in the configuration view header * The URL object is not compared properly by lodash. This meant, we were a host change event whenever anything unrelated in the bundle changed. Which lead to reconnection on every edit to the bundle. Also fix this.
1 parent e5f0780 commit fa4013c

File tree

9 files changed

+149
-86
lines changed

9 files changed

+149
-86
lines changed

packages/databricks-vscode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@
343343
},
344344
{
345345
"command": "databricks.bundle.initNewProject",
346+
"group": "navigation@1",
346347
"when": "view == configurationView"
347348
},
348349
{

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

Lines changed: 87 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,25 @@ export class LoginWizard {
7272
});
7373
}
7474

75+
const hostToProfilesMap = new Map<string, string[]>();
76+
(await this.getProfiles()).forEach((profile) => {
77+
hostToProfilesMap.set(
78+
profile.host!.toString(),
79+
(hostToProfilesMap.get(profile.host!.toString()) ?? []).concat(
80+
profile.name
81+
)
82+
);
83+
});
84+
7585
items.push(
76-
...(await this.getProfiles()).map((profile) => {
77-
return {
78-
label: profile.host!.toString(),
79-
detail: `Profile: ${profile.name}`,
80-
};
81-
})
86+
...Array.from(hostToProfilesMap.entries()).map(
87+
([host, profiles]) => {
88+
return {
89+
label: host,
90+
detail: `Profiles: ${profiles.join(", ")}`,
91+
};
92+
}
93+
)
8294
);
8395

8496
const host = await input.showQuickAutoComplete({
@@ -122,16 +134,64 @@ export class LoginWizard {
122134
throw InputFlowAction.cancel;
123135
}
124136

137+
private async getProfileQuickPickItems() {
138+
const items: Array<AuthTypeQuickPickItem> = [];
139+
140+
const profiles = (await this.getProfiles())
141+
.filter((profile) => {
142+
return profile.host?.hostname === this.state.host!.hostname;
143+
})
144+
.map((profile) => {
145+
const humanisedAuthType = humaniseSdkAuthType(profile.authType);
146+
const detail = humanisedAuthType
147+
? `Authenticate using ${humaniseSdkAuthType(
148+
profile.authType
149+
)}`
150+
: `Authenticate using profile ${profile.name}`;
151+
152+
return {
153+
label: profile.name,
154+
detail,
155+
authType: profile.authType as SdkAuthType,
156+
profile: profile.name,
157+
};
158+
});
159+
160+
if (profiles.length !== 0) {
161+
items.push(
162+
{
163+
label: "Existing Databricks CLI Profiles",
164+
kind: QuickPickItemKind.Separator,
165+
},
166+
...profiles
167+
);
168+
}
169+
return items;
170+
}
125171
private async selectAuthMethod(
126172
input: MultiStepInput
127173
): Promise<InputStep | void> {
128174
const items: Array<AuthTypeQuickPickItem> = [];
129-
items.push({
130-
label: "Create new Databricks CLI profile",
131-
kind: QuickPickItemKind.Separator,
132-
});
133-
for (const authMethod of authMethodsForHostname(this.state.host!)) {
175+
items.push(...(await this.getProfileQuickPickItems()));
176+
177+
const availableAuthMethods = authMethodsForHostname(this.state.host!);
178+
if (availableAuthMethods.length !== 0) {
179+
items.push({
180+
label: "Create New Databricks CLI Profile",
181+
kind: QuickPickItemKind.Separator,
182+
});
183+
}
184+
185+
for (const authMethod of availableAuthMethods) {
134186
switch (authMethod) {
187+
case "pat":
188+
items.push({
189+
label: "Personal Access Token",
190+
detail: "Create a profile and authenticate using a Personal Access Token",
191+
authType: "pat",
192+
});
193+
break;
194+
135195
case "azure-cli":
136196
items.push({
137197
label: "Azure CLI",
@@ -147,67 +207,24 @@ export class LoginWizard {
147207
authType: "databricks-cli",
148208
});
149209
break;
150-
case "profile":
151-
{
152-
const profiles = (await this.getProfiles())
153-
.filter((profile) => {
154-
return (
155-
profile.host?.hostname ===
156-
this.state.host!.hostname
157-
);
158-
})
159-
.map((profile) => {
160-
const humanisedAuthType = humaniseSdkAuthType(
161-
profile.authType
162-
);
163-
const detail = humanisedAuthType
164-
? `Authenticate using ${humaniseSdkAuthType(
165-
profile.authType
166-
)}`
167-
: `Authenticate using profile ${profile.name}`;
168-
169-
return {
170-
label: profile.name,
171-
detail,
172-
authType: profile.authType as SdkAuthType,
173-
profile: profile.name,
174-
};
175-
});
176-
177-
items.push({
178-
label: "Personal Access Token",
179-
detail: "Create a profile and authenticate using a Personal Access Token",
180-
authType: "pat",
181-
});
182-
if (profiles.length !== 0) {
183-
items.push(
184-
{
185-
label: "Existing Databricks CLI Profiles",
186-
kind: QuickPickItemKind.Separator,
187-
},
188-
...profiles
189-
);
190-
}
191-
192-
items.push({
193-
label: "",
194-
kind: QuickPickItemKind.Separator,
195-
});
196-
197-
items.push({
198-
label: "Edit Databricks profiles",
199-
detail: "Open ~/.databrickscfg to create or edit profiles",
200-
openDatabricksConfigFile: true,
201-
});
202-
}
203-
204-
break;
205210

206211
default:
207212
break;
208213
}
209214
}
210215

216+
items.push(
217+
{
218+
label: "",
219+
kind: QuickPickItemKind.Separator,
220+
},
221+
{
222+
label: "Edit Databricks profiles",
223+
detail: "Open ~/.databrickscfg to create or edit profiles",
224+
openDatabricksConfigFile: true,
225+
}
226+
);
227+
211228
const pick: AuthTypeQuickPickItem = await input.showQuickPick({
212229
title: this.title,
213230
step: 2,
@@ -452,18 +469,18 @@ async function validateDatabricksHost(
452469

453470
function authMethodsForHostname(host: URL): Array<AuthType> {
454471
if (UrlUtils.isAzureHost(host)) {
455-
return ["azure-cli", "profile"];
472+
return ["azure-cli", "pat"];
456473
}
457474

458475
if (UrlUtils.isGcpHost(host)) {
459-
return ["profile"];
476+
return ["pat"];
460477
}
461478

462479
if (UrlUtils.isAwsHost(host)) {
463-
return ["databricks-cli", "profile"];
480+
return ["databricks-cli", "pat"];
464481
}
465482

466-
return ["profile"];
483+
return ["pat"];
467484
}
468485

469486
async function collectTokenForPatAuth(

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export class ConfigModel implements Disposable {
146146
);
147147
}
148148

149-
@onError({popup: {prefix: "Failed to initialize configs."}})
149+
@onError({popup: true})
150150
public async init() {
151151
await this.readTarget();
152152
this.bundleRemoteStateModel.init();
@@ -177,7 +177,18 @@ export class ConfigModel implements Disposable {
177177
}
178178
savedTarget = await this.bundlePreValidateModel.defaultTarget;
179179
});
180-
await this.setTarget(savedTarget);
180+
181+
try {
182+
await this.setTarget(savedTarget);
183+
} catch (e: any) {
184+
let message: string = String(e);
185+
if (e instanceof Error) {
186+
message = e.message;
187+
}
188+
throw new Error(
189+
`Failed to initialize target ${savedTarget}: ${message}`
190+
);
191+
}
181192
}
182193

183194
public get target() {

packages/databricks-vscode/src/locking/CachedValue.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,24 @@ export class CachedValue<T> implements Disposable {
3838
newValue = {} as T;
3939
}
4040

41+
const customizer: lodash.IsEqualCustomizer = (value, other) => {
42+
if (value instanceof URL && other instanceof URL) {
43+
return value.toString() === other.toString();
44+
}
45+
return undefined;
46+
};
47+
4148
for (const key of Object.keys({
4249
...oldValue,
4350
...newValue,
4451
} as any) as (keyof T)[]) {
4552
if (
4653
oldValue === null ||
47-
!lodash.isEqual(oldValue?.[key], newValue?.[key])
54+
!lodash.isEqualWith(
55+
oldValue?.[key],
56+
newValue?.[key],
57+
customizer
58+
)
4859
) {
4960
this.onDidChangeKeyEmitters.get(key)?.fire();
5061
}

packages/databricks-vscode/src/ui/bundle-resource-explorer/DecorationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class TreeItemDecorationProvider implements FileDecorationProvider {
5151
export function asDecorationResourceUri(id: string, data: FileDecoration) {
5252
return Uri.from({
5353
scheme: SCHEME,
54-
authority: id,
54+
path: id,
5555
query: JSON.stringify(data),
5656
});
5757
}

packages/databricks-vscode/src/ui/bundle-resource-explorer/JobTreeNode.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {TreeItemCollapsibleState, ExtensionContext} from "vscode";
1+
import {ExtensionContext} from "vscode";
22
import {BundleRemoteState} from "../../bundle/models/BundleRemoteStateModel";
33
import {
44
BundleResourceExplorerResource,
@@ -48,15 +48,18 @@ export class JobTreeNode implements BundleResourceExplorerTreeNode {
4848
resourceType: this.type,
4949
running: isRunning,
5050
hasUrl: this.url !== undefined,
51+
cancellable: isRunning,
5152
nodeType: this.type,
53+
modifiedStatus: this.data.modified_status,
5254
}),
5355
resourceUri: DecorationUtils.getModifiedStatusDecoration(
54-
this.resourceKey,
56+
this.data.name ?? this.resourceKey,
57+
this.data.modified_status
58+
),
59+
collapsibleState: DecorationUtils.getCollapsibleState(
60+
isRunning,
5561
this.data.modified_status
5662
),
57-
collapsibleState: isRunning
58-
? TreeItemCollapsibleState.Collapsed
59-
: TreeItemCollapsibleState.Expanded,
6063
};
6164
}
6265

packages/databricks-vscode/src/ui/bundle-resource-explorer/PipelineTreeNode.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {TreeItemCollapsibleState} from "vscode";
21
import {BundleRemoteState} from "../../bundle/models/BundleRemoteStateModel";
32
import {
43
BundleResourceExplorerResource,
@@ -46,15 +45,18 @@ export class PipelineTreeNode implements BundleResourceExplorerTreeNode {
4645
resourceType: this.type,
4746
running: isRunning,
4847
hasUrl: this.url !== undefined,
48+
cancellable: isRunning,
4949
nodeType: this.type,
50+
modifiedStatus: this.data.modified_status,
5051
}),
5152
resourceUri: DecorationUtils.getModifiedStatusDecoration(
52-
this.resourceKey,
53+
this.data.name ?? this.resourceKey,
54+
this.data.modified_status
55+
),
56+
collapsibleState: DecorationUtils.getCollapsibleState(
57+
isRunning,
5358
this.data.modified_status
5459
),
55-
collapsibleState: isRunning
56-
? TreeItemCollapsibleState.Expanded
57-
: TreeItemCollapsibleState.Collapsed,
5860
};
5961
}
6062

packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/ContextUtils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {BundleResourceModifiedStatus} from "../../../bundle/models/BundleRemoteStateModel";
12
import {RUNNABLE_BUNDLE_RESOURCES} from "../BundleCommands";
23
import {
34
BundleResourceExplorerResourceKey,
@@ -10,6 +11,7 @@ type BundleTreeItemContext = {
1011
running?: boolean;
1112
cancellable?: boolean;
1213
hasUrl?: boolean;
14+
modifiedStatus?: BundleResourceModifiedStatus;
1315
};
1416

1517
export function getContextString(context: BundleTreeItemContext) {
@@ -21,7 +23,8 @@ export function getContextString(context: BundleTreeItemContext) {
2123
(RUNNABLE_BUNDLE_RESOURCES as string[]).includes(
2224
context.resourceType
2325
) &&
24-
!context.running
26+
!context.running &&
27+
context.modifiedStatus !== "deleted"
2528
) {
2629
parts.push("runnable");
2730
}

packages/databricks-vscode/src/ui/bundle-resource-explorer/utils/DecorationUtils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {BundleResourceModifiedStatus} from "../../../bundle/models/BundleRemoteStateModel";
22
import {asDecorationResourceUri} from "../DecorationProvider";
3-
import {ThemeColor} from "vscode";
3+
import {ThemeColor, TreeItemCollapsibleState} from "vscode";
44

55
export function getModifiedStatusDecoration(
66
id: string,
@@ -31,3 +31,18 @@ export function getModifiedStatusDecoration(
3131
});
3232
}
3333
}
34+
35+
export function getCollapsibleState(
36+
isRunning: boolean,
37+
modifiedStatus?: BundleResourceModifiedStatus
38+
): TreeItemCollapsibleState {
39+
if (modifiedStatus === "deleted") {
40+
return TreeItemCollapsibleState.None;
41+
}
42+
43+
if (isRunning) {
44+
return TreeItemCollapsibleState.Collapsed;
45+
}
46+
47+
return TreeItemCollapsibleState.Expanded;
48+
}

0 commit comments

Comments
 (0)