Skip to content

Commit c769e26

Browse files
authored
Provide an option to hook up DTS emulator when locally debugging DTS projects (#4538)
1 parent 13c1d2f commit c769e26

20 files changed

+388
-33
lines changed

src/commands/appSettings/connectionSettings/IConnectionPromptOptions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues, type StorageConnectionTypeValues } from "../../../constants";
6+
import { type EventHubsConnectionType, type SqlDbConnectionType, type StorageConnectionType } from "./IConnectionTypesContext";
7+
78

89
export interface IConnectionPromptOptions {
9-
preselectedConnectionType?: StorageConnectionTypeValues | EventHubsConnectionTypeValues | SqlDbConnectionTypeValues;
10+
preselectedConnectionType?: StorageConnectionType | EventHubsConnectionType | SqlDbConnectionType;
1011
}
1112

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type ConnectionType } from "../../../constants";
7+
8+
export type StorageConnectionType = ConnectionType.Azure | ConnectionType.Emulator;
9+
export type DTSConnectionType = ConnectionType;
10+
export type EventHubsConnectionType = ConnectionType.Azure | ConnectionType.Emulator;
11+
export type SqlDbConnectionType = ConnectionType.Azure | ConnectionType.Custom;
12+
13+
export interface IConnectionTypesContext {
14+
azureWebJobsStorageType?: StorageConnectionType;
15+
dtsConnectionType?: DTSConnectionType;
16+
eventHubsConnectionType?: EventHubsConnectionType;
17+
sqlDbConnectionType?: SqlDbConnectionType;
18+
}
19+

src/commands/appSettings/connectionSettings/ISetConnectionSettingContext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
import { type IActionContext } from "@microsoft/vscode-azext-utils";
77
import { type CodeActionValues, type ConnectionKey } from "../../../constants";
8+
import { type IConnectionTypesContext } from "./IConnectionTypesContext";
89

9-
export interface ISetConnectionSettingContext extends IActionContext {
10+
export interface ISetConnectionSettingContext extends IActionContext, IConnectionTypesContext {
1011
action: CodeActionValues;
1112
projectPath: string;
1213

src/commands/appSettings/connectionSettings/azureWebJobsStorage/AzureWebJobsStoragePromptStep.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import { StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication } from '@microsoft/vscode-azext-azureutils';
77
import { AzureWizardPromptStep, type ISubscriptionActionContext, type IWizardOptions } from '@microsoft/vscode-azext-utils';
88
import { type MessageItem } from 'vscode';
9-
import { ConnectionType, type EventHubsConnectionTypeValues, type SqlDbConnectionTypeValues } from '../../../../constants';
9+
import { ConnectionType } from '../../../../constants';
1010
import { useEmulator } from '../../../../constants-nls';
1111
import { ext } from '../../../../extensionVariables';
1212
import { localize } from '../../../../localize';
1313
import { type IConnectionPromptOptions } from '../IConnectionPromptOptions';
14+
import { type StorageConnectionType } from '../IConnectionTypesContext';
1415
import { type IAzureWebJobsStorageWizardContext } from './IAzureWebJobsStorageWizardContext';
1516

1617
export class AzureWebJobsStoragePromptStep<T extends IAzureWebJobsStorageWizardContext> extends AzureWizardPromptStep<T> {
@@ -36,17 +37,16 @@ export class AzureWebJobsStoragePromptStep<T extends IAzureWebJobsStorageWizardC
3637
context.telemetry.properties.azureWebJobsStorageType = context.azureWebJobsStorageType;
3738
}
3839

39-
public async configureBeforePrompt(context: T & { eventHubsConnectionType?: EventHubsConnectionTypeValues, sqlDbConnectionType?: SqlDbConnectionTypeValues }): Promise<void> {
40+
public async configureBeforePrompt(context: T): Promise<void> {
41+
const matchingConnectionType: StorageConnectionType | undefined = tryFindMatchingConnectionType([context.dtsConnectionType, context.eventHubsConnectionType, context.sqlDbConnectionType]);
42+
4043
if (this.options?.preselectedConnectionType === ConnectionType.Azure || this.options?.preselectedConnectionType === ConnectionType.Emulator) {
4144
context.azureWebJobsStorageType = this.options.preselectedConnectionType;
4245
} else if (!!context.storageAccount || !!context.newStorageAccountName) {
4346
// Only should prompt if no storage account was selected
4447
context.azureWebJobsStorageType = ConnectionType.Azure;
45-
} else if (context.eventHubsConnectionType) {
46-
context.azureWebJobsStorageType = context.eventHubsConnectionType;
47-
} else if (context.sqlDbConnectionType === ConnectionType.Azure) {
48-
// No official support for an `Emulator` scenario yet
49-
context.azureWebJobsStorageType = context.sqlDbConnectionType;
48+
} else if (matchingConnectionType) {
49+
context.azureWebJobsStorageType = matchingConnectionType;
5050
}
5151

5252
// Even if we end up skipping the prompt, we should still record the flow in telemetry
@@ -88,3 +88,18 @@ export class AzureWebJobsStoragePromptStep<T extends IAzureWebJobsStorageWizardC
8888
return { promptSteps };
8989
}
9090
}
91+
92+
const availableStorageConnections: Set<ConnectionType> = new Set([ConnectionType.Azure, ConnectionType.Emulator]);
93+
94+
function tryFindMatchingConnectionType(connections: (ConnectionType | undefined)[]): StorageConnectionType | undefined {
95+
for (const c of connections) {
96+
if (!c) {
97+
continue;
98+
}
99+
100+
if (availableStorageConnections.has(c)) {
101+
return c as StorageConnectionType;
102+
}
103+
}
104+
return undefined;
105+
}

src/commands/appSettings/connectionSettings/azureWebJobsStorage/IAzureWebJobsStorageWizardContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
import { type StorageAccount } from "@azure/arm-storage";
77
import { type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
8-
import { type StorageConnectionTypeValues } from "../../../../constants";
8+
import { type StorageConnectionType } from "../IConnectionTypesContext";
99
import { type ISetConnectionSettingContext } from "../ISetConnectionSettingContext";
1010

1111
export interface IAzureWebJobsStorageWizardContext extends ISetConnectionSettingContext, Partial<ISubscriptionContext> {
1212
storageAccount?: StorageAccount;
1313
newStorageAccountName?: string;
1414

15-
azureWebJobsStorageType?: StorageConnectionTypeValues;
15+
azureWebJobsStorageType?: StorageConnectionType;
1616
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep, validationUtils } from '@microsoft/vscode-azext-utils';
7+
import { ConnectionType } from '../../../../constants';
8+
import { localize } from '../../../../localize';
9+
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
10+
11+
export class DTSConnectionCustomPromptStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
12+
public async prompt(context: T): Promise<void> {
13+
context.newDTSConnection = (await context.ui.showInputBox({
14+
prompt: localize('customDTSConnectionPrompt', 'Provide a custom DTS connection string.'),
15+
validateInput: (value: string | undefined) => this.validateInput(value)
16+
})).trim();
17+
}
18+
19+
public shouldPrompt(context: T): boolean {
20+
return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Custom;
21+
}
22+
23+
private validateInput(name: string | undefined): string | undefined {
24+
name = name ? name.trim() : '';
25+
if (!validationUtils.hasValidCharLength(name)) {
26+
return validationUtils.getInvalidCharLengthMessage();
27+
}
28+
return undefined;
29+
}
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { nonNullProp } from '@microsoft/vscode-azext-utils';
7+
import { ConnectionKey } from '../../../../constants';
8+
import { SetConnectionSettingStepBase } from '../SetConnectionSettingStepBase';
9+
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
10+
11+
export class DTSConnectionSetSettingStep<T extends IDTSConnectionWizardContext> extends SetConnectionSettingStepBase<T> {
12+
public priority: number = 240;
13+
public debugDeploySetting: ConnectionKey = ConnectionKey.DTS;
14+
15+
public async execute(context: T): Promise<void> {
16+
await this.setConnectionSetting(context, nonNullProp(context, 'newDTSConnection'));
17+
}
18+
19+
public shouldExecute(context: T): boolean {
20+
return !!context.newDTSConnection;
21+
}
22+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep, type AzureWizardExecuteStep, type IWizardOptions } from '@microsoft/vscode-azext-utils';
7+
import { type MessageItem } from 'vscode';
8+
import { ConnectionType } from '../../../../constants';
9+
import { useEmulator } from '../../../../constants-nls';
10+
import { localize } from '../../../../localize';
11+
import { DTSConnectionCustomPromptStep } from './DTSConnectionCustomPromptStep';
12+
import { DTSConnectionSetSettingStep } from './DTSConnectionSetSettingStep';
13+
import { DTSEmulatorStartStep } from './DTSEmulatorStartStep';
14+
import { DTSHubNameCustomPromptStep } from './DTSHubNameCustomPromptStep';
15+
import { DTSHubNameSetSettingStep } from './DTSHubNameSetSettingStep';
16+
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
17+
18+
export class DTSConnectionTypeListStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
19+
constructor(readonly connectionTypes: Set<ConnectionType>) {
20+
super();
21+
}
22+
23+
public async prompt(context: T): Promise<void> {
24+
const connectAzureButton = { title: localize('connectAzureTaskScheduler', 'Connect Azure Task Scheduler'), data: ConnectionType.Azure };
25+
const connectEmulatorButton = { title: useEmulator, data: ConnectionType.Emulator };
26+
const connectCustomDTSButton = { title: localize('connectCustomTaskScheduler', 'Manually Set a Connection String'), data: ConnectionType.Custom };
27+
28+
const buttons: MessageItem[] = [];
29+
if (this.connectionTypes.has(ConnectionType.Azure)) {
30+
buttons.push(connectAzureButton);
31+
}
32+
if (this.connectionTypes.has(ConnectionType.Emulator)) {
33+
buttons.push(connectEmulatorButton);
34+
}
35+
if (this.connectionTypes.has(ConnectionType.Custom)) {
36+
buttons.push(connectCustomDTSButton);
37+
}
38+
39+
const message: string = localize('selectDTSConnection', 'Durable Functions needs to be configured to use a Durable Task Scheduler.');
40+
context.dtsConnectionType = (await context.ui.showWarningMessage(message, { modal: true }, ...buttons) as {
41+
title: string;
42+
data: ConnectionType;
43+
}).data;
44+
45+
context.telemetry.properties.dtsConnectionType = context.dtsConnectionType;
46+
}
47+
48+
public shouldPrompt(context: T): boolean {
49+
return !context.dtsConnectionType;
50+
}
51+
52+
public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
53+
const promptSteps: AzureWizardPromptStep<T>[] = [];
54+
const executeSteps: AzureWizardExecuteStep<T>[] = [];
55+
56+
switch (context.dtsConnectionType) {
57+
case ConnectionType.Azure:
58+
throw new Error('Needs implementation.');
59+
case ConnectionType.Emulator:
60+
executeSteps.push(new DTSEmulatorStartStep());
61+
break;
62+
case ConnectionType.Custom:
63+
promptSteps.push(
64+
new DTSConnectionCustomPromptStep(),
65+
new DTSHubNameCustomPromptStep(),
66+
);
67+
break;
68+
}
69+
70+
executeSteps.push(
71+
new DTSConnectionSetSettingStep(),
72+
new DTSHubNameSetSettingStep(),
73+
);
74+
75+
return { promptSteps, executeSteps };
76+
}
77+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardExecuteStep, nonNullValue } from '@microsoft/vscode-azext-utils';
7+
import { commands, window } from 'vscode';
8+
import { ConnectionType } from '../../../../constants';
9+
import { ext } from '../../../../extensionVariables';
10+
import { localize } from '../../../../localize';
11+
import { type DurableTaskSchedulerEmulator } from '../../../../tree/durableTaskScheduler/DurableTaskSchedulerEmulatorClient';
12+
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
13+
14+
export class DTSEmulatorStartStep<T extends IDTSConnectionWizardContext> extends AzureWizardExecuteStep<T> {
15+
public priority: number = 200;
16+
17+
public async execute(context: T): Promise<void> {
18+
const emulatorId: string = nonNullValue(
19+
await commands.executeCommand('azureFunctions.durableTaskScheduler.startEmulator'),
20+
localize('failedToStartEmulator', 'Internal error: Failed to start DTS emulator.'),
21+
);
22+
23+
const emulators: DurableTaskSchedulerEmulator[] = nonNullValue(
24+
await commands.executeCommand('azureFunctions.durableTaskScheduler.getEmulators'),
25+
localize('failedToGetEmulators', 'Internal error: Failed to retrieve the list of DTS emulators.'),
26+
);
27+
28+
const emulator: DurableTaskSchedulerEmulator = nonNullValue(
29+
emulators.find(e => e.id === emulatorId),
30+
localize('couldNotFindEmulator', 'Internal error: Failed to retrieve info on the started DTS emulator.'),
31+
);
32+
33+
const { schedulerEndpoint, dashboardEndpoint } = emulator;
34+
35+
const message: string = localize('emulatorStartedMessage', `Durable Task Scheduler (DTS) emulator has been started by your container client at "{0}". Its dashboard is available at "{1}".`, schedulerEndpoint.toString(), dashboardEndpoint.toString());
36+
void window.showInformationMessage(message);
37+
ext.outputChannel.appendLog(message);
38+
39+
context.newDTSConnection = `Endpoint=${emulator.schedulerEndpoint};Authentication=None`;
40+
context.newDTSHubName = 'default';
41+
}
42+
43+
public shouldExecute(context: T): boolean {
44+
return !context.newDTSConnection && context.dtsConnectionType === ConnectionType.Emulator;
45+
}
46+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep, validationUtils, type IActionContext } from '@microsoft/vscode-azext-utils';
7+
import * as path from 'path';
8+
import { ConnectionKey, ConnectionType, localSettingsFileName } from '../../../../constants';
9+
import { getLocalSettingsJson } from '../../../../funcConfig/local.settings';
10+
import { localize } from '../../../../localize';
11+
import { type IDTSConnectionWizardContext } from './IDTSConnectionWizardContext';
12+
13+
export class DTSHubNameCustomPromptStep<T extends IDTSConnectionWizardContext> extends AzureWizardPromptStep<T> {
14+
public async prompt(context: T): Promise<void> {
15+
context.newDTSHubName = (await context.ui.showInputBox({
16+
prompt: localize('customDTSConnectionPrompt', 'Provide the custom DTS hub name.'),
17+
value: await getDTSHubName(context, context.projectPath),
18+
validateInput: (value: string) => this.validateInput(value)
19+
})).trim();
20+
}
21+
22+
public shouldPrompt(context: T): boolean {
23+
return !context.newDTSHubName && context.dtsConnectionType === ConnectionType.Custom;
24+
}
25+
26+
private validateInput(name: string): string | undefined {
27+
name = name.trim();
28+
29+
if (!validationUtils.hasValidCharLength(name)) {
30+
return validationUtils.getInvalidCharLengthMessage();
31+
}
32+
return undefined;
33+
}
34+
}
35+
36+
async function getDTSHubName(context: IActionContext, projectPath: string): Promise<string | undefined> {
37+
const localSettingsJson = await getLocalSettingsJson(context, path.join(projectPath, localSettingsFileName));
38+
return localSettingsJson.Values?.[ConnectionKey.DTSHub];
39+
}

0 commit comments

Comments
 (0)