Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1d32cd2
Scaffold BDP.
philliphoff Jan 13, 2025
4d9864d
Refactor type hierarchy.
philliphoff Jan 13, 2025
7df4864
Sketch retrieval of task hubs.
philliphoff Jan 14, 2025
9212ddc
Update task hub icon.
philliphoff Jan 14, 2025
c59c09a
Enable "open in portal" command for task hubs.
philliphoff Jan 14, 2025
97ab3c7
Scaffold "open in dashboard" command.
philliphoff Jan 14, 2025
ad68a74
Sketch "open in dashboard" implementation.
philliphoff Jan 14, 2025
9f457a5
Move DTS management to separate client type.
philliphoff Jan 14, 2025
44c4d45
Support viewing task hub properties.
philliphoff Jan 14, 2025
045a889
Split apart types.
philliphoff Jan 14, 2025
56e0657
Add file headers.
philliphoff Jan 14, 2025
872b8fb
Consolidate client logic and add localizable strings.
philliphoff Jan 14, 2025
f3d7506
Merge branch 'main' into philliphoff-dts-nodes
philliphoff Jan 14, 2025
bb92c76
Scaffold creation of task hub.
philliphoff Jan 25, 2025
f36b3c3
Sketch creation of schedulers.
philliphoff Jan 27, 2025
c986f37
Merge branch 'main' into philliphoff-manage-schedulers
philliphoff Jan 27, 2025
5008def
Expose DTS creation from common new menu.
philliphoff Jan 27, 2025
b22d97d
Sketch deletion of task hubs.
philliphoff Jan 28, 2025
2bf4748
Sketch deletion of schedulers.
philliphoff Jan 28, 2025
c741264
Sketch refreshing models post-creation.
philliphoff Jan 30, 2025
e3f1125
Add tree refresh to remainder of DTS commands.
philliphoff Jan 30, 2025
0fe25f8
Provide extended schduler properties.
philliphoff Jan 30, 2025
18fca77
Add provisioning state when not "normal".
philliphoff Jan 30, 2025
19f70c2
Move creation command to top of context menu.
philliphoff Jan 30, 2025
db2d957
Add copy scheduler endpoint command.
philliphoff Jan 30, 2025
5e7d1ea
Sketch copy connections string command.
philliphoff Jan 31, 2025
b61e5f5
Add task hub selection for connection string.
philliphoff Jan 31, 2025
abe8d29
Add async waits.
philliphoff Jan 31, 2025
92c3f12
Wrap deletion in an activity.
philliphoff Feb 3, 2025
bbc47c0
Add some robustness to API call failures.
philliphoff Feb 3, 2025
992c2f8
Tweak strings for task hub selection.
philliphoff Feb 3, 2025
b3f2022
Wrap task hub creation with activity.
philliphoff Feb 3, 2025
0d5ee1a
Wrap task hub deletion with activity.
philliphoff Feb 3, 2025
3b52269
Add retry for task hub retrieval.
philliphoff Feb 3, 2025
2c9cd5f
Hide DTS commands from palette.
philliphoff Feb 4, 2025
85fe2bc
Add verify providers step to scheduler creation.
philliphoff Feb 4, 2025
dc569e8
Un-hide create scheduler command.
philliphoff Feb 4, 2025
6407f41
Sketch DTS setting.
philliphoff Feb 4, 2025
0370550
Refactor DTS preview setting name.
philliphoff Feb 4, 2025
b8ae76e
Add preview features enabled check during create.
philliphoff Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
"resources": true
},
"commands": [
{
"command": "azureFunctions.durableTaskScheduler.createScheduler",
"title": "%azureFunctions.durableTaskScheduler.createScheduler%",
"type": "DurableTaskScheduler",
"detail": "%azureFunctions.durableTaskScheduler.createScheduler.detail%"
},
{
"command": "azureFunctions.createFunctionApp",
"title": "%azureFunctions.createFunctionApp%",
Expand Down Expand Up @@ -375,6 +381,36 @@
"category": "Azure Functions",
"icon": "$(notebook-execute)"
},
{
"command": "azureFunctions.durableTaskScheduler.copySchedulerConnectionString",
"title": "%azureFunctions.durableTaskScheduler.copySchedulerConnectionString%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.copySchedulerEndpoint",
"title": "%azureFunctions.durableTaskScheduler.copySchedulerEndpoint%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.createScheduler",
"title": "%azureFunctions.durableTaskScheduler.createScheduler%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.createTaskHub",
"title": "%azureFunctions.durableTaskScheduler.createTaskHub%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.deleteScheduler",
"title": "%azureFunctions.durableTaskScheduler.deleteScheduler%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.deleteTaskHub",
"title": "%azureFunctions.durableTaskScheduler.deleteTaskHub%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.durableTaskScheduler.openTaskHubDashboard",
"title": "%azureFunctions.durableTaskScheduler.openTaskHubDashboard%",
Expand Down Expand Up @@ -673,9 +709,40 @@
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.*folder/",
"group": "1@1"
},
{
"command": "azureFunctions.durableTaskScheduler.copySchedulerConnectionString",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.scheduler/",
"group": "3@1"
},
{
"command": "azureFunctions.durableTaskScheduler.copySchedulerEndpoint",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.scheduler/",
"group": "3@2"
},
{
"command": "azureFunctions.durableTaskScheduler.createScheduler",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /DurableTaskScheduler/i && viewItem =~ /azureResourceTypeGroup/i",
"group": "1@1"
},
{
"command": "azureFunctions.durableTaskScheduler.createTaskHub",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.scheduler/",
"group": "1@1"
},
{
"command": "azureFunctions.durableTaskScheduler.deleteScheduler",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.scheduler/",
"group": "2@1"
},
{
"command": "azureFunctions.durableTaskScheduler.deleteTaskHub",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.taskHub/",
"group": "2@1"
},
{
"command": "azureFunctions.durableTaskScheduler.openTaskHubDashboard",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.taskHub/"
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.taskHub/",
"group": "1@1"
}
],
"explorer/context": [
Expand Down
7 changes: 7 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,12 @@
"azureFunctions.walkthrough.functionsStart.scenarios.title": "Explore common scenarios",
"azureFunctions.walkthrough.functionsStart.title": "Get Started with Azure Functions",

"azureFunctions.durableTaskScheduler.copySchedulerConnectionString": "Copy Connection String",
"azureFunctions.durableTaskScheduler.copySchedulerEndpoint": "Copy Endpoint",
"azureFunctions.durableTaskScheduler.createScheduler": "Create Durable Task Scheduler...",
"azureFunctions.durableTaskScheduler.createScheduler.detail": "For long-running and reliable workflows.",
"azureFunctions.durableTaskScheduler.createTaskHub": "Create Task Hub...",
"azureFunctions.durableTaskScheduler.deleteScheduler": "Delete Scheduler...",
"azureFunctions.durableTaskScheduler.deleteTaskHub": "Delete Task Hub...",
"azureFunctions.durableTaskScheduler.openTaskHubDashboard": "Open in Dashboard"
}
117 changes: 117 additions & 0 deletions src/commands/durableTaskScheduler/copySchedulerConnectionString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type IActionContext } from "@microsoft/vscode-azext-utils";
import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient";
import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel";
import { localize } from "../../localize";
import { ext } from "../../extensionVariables";
import { env, QuickPickItemKind, type QuickPickItem } from "vscode";

export function copySchedulerConnectionStringCommandFactory(schedulerClient: DurableTaskSchedulerClient) {
return async (actionContext: IActionContext, scheduler: DurableTaskSchedulerResourceModel | undefined): Promise<void> => {
if (!scheduler) {
throw new Error(localize('noSchedulerSelectedErrorMessage', 'No scheduler was selected.'));
}

const schedulerJson = await schedulerClient.getScheduler(
scheduler.subscription,
scheduler.resourceGroup,
scheduler.name);

if (!schedulerJson) {
throw new Error(localize('schedulerNotFoundErrorMessage', 'Scheduler does not exist.'));
}

const { endpoint } = schedulerJson.properties;

const noAuthentication: QuickPickItem = {
detail: localize('noAuthenticationDetail', 'No credentials will be used.'),
label: localize('noAuthenticationLabel', 'None')
}

const localDevelopment: QuickPickItem = {
detail: localize('localDevelopmentDetail', 'Use the credentials of the local developer.'),
label: localize('localDevelopmentLabel', 'Local development')
};

const userAssignedManagedIdentity: QuickPickItem = {
detail: localize('userAssignedManagedIdentityDetail', 'Use managed identity credentials for a specific client.'),
label: localize('userAssignedManagedIdentityLabel', 'User-assigned managed identity')
}

const systemAssignedManagedIdentity: QuickPickItem = {
detail: localize('systemAssignedManagedIdentityDetail', 'Use managed identity credentials for a client assigned to a specific Azure resource.'),
label: localize('systemAssignedManagedIdentityLabel', 'System-assigned managed identity')
}

const result = await actionContext.ui.showQuickPick(
[
noAuthentication,
localDevelopment,
userAssignedManagedIdentity,
systemAssignedManagedIdentity
],
{
canPickMany: false,
placeHolder: localize('authenticationTypePlaceholder', 'Select the credentials to be used to connect to the scheduler')
});

let connectionString = `Endpoint=${endpoint};Authentication=`

if (result === noAuthentication) {
connectionString += 'None';
}
else if (result === localDevelopment) {
connectionString += 'DefaultAzure';
}
else {
connectionString += 'ManagedIdentity';

if (result === userAssignedManagedIdentity) {
connectionString += ';ClientID=<ClientID>';
}
}

const taskHubs = await schedulerClient.getSchedulerTaskHubs(
scheduler.subscription,
scheduler.resourceGroup,
scheduler.name);

if (taskHubs.length > 0) {

const noTaskHubItem: QuickPickItem = {
detail: localize('noTaskHubDetail', 'Do not connect to a specific task hub.'),
label: localize('noTaskHubLabel', 'None')
}

const taskHubItems: QuickPickItem[] =
taskHubs.map(taskHub => ({ label: taskHub.name }));

const taskHubResult = await actionContext.ui.showQuickPick(
[
noTaskHubItem,
{
kind: QuickPickItemKind.Separator,
label: localize('taskHubSepratorLabel', 'Task Hubs')
},
...taskHubItems
],
{
canPickMany: false,
placeHolder: localize('taskHubSelectionPlaceholder', 'Select a task hub to connect to')
});

if (taskHubResult && taskHubResult !== noTaskHubItem) {
connectionString += `;TaskHub=${taskHubResult.label}`;
}
}

await env.clipboard.writeText(connectionString);

ext.outputChannel.show();
ext.outputChannel.appendLog(localize('schedulerConnectionStringCopiedMessage', 'Connection string copied to clipboard: {0}', connectionString));
}
}
35 changes: 35 additions & 0 deletions src/commands/durableTaskScheduler/copySchedulerEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type IActionContext } from "@microsoft/vscode-azext-utils";
import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient";
import { type DurableTaskSchedulerResourceModel } from "../../tree/durableTaskScheduler/DurableTaskSchedulerResourceModel";
import { localize } from "../../localize";
import { ext } from "../../extensionVariables";
import { env } from "vscode";

export function copySchedulerEndpointCommandFactory(schedulerClient: DurableTaskSchedulerClient) {
return async (_: IActionContext, scheduler: DurableTaskSchedulerResourceModel | undefined): Promise<void> => {
if (!scheduler) {
throw new Error(localize('noSchedulerSelectedErrorMessage', 'No scheduler was selected.'));
}

const schedulerJson = await schedulerClient.getScheduler(
scheduler.subscription,
scheduler.resourceGroup,
scheduler.name);

if (!schedulerJson) {
throw new Error(localize('schedulerNotFoundErrorMessage', 'Scheduler does not exist.'));
}

const { endpoint } = schedulerJson.properties;

await env.clipboard.writeText(endpoint);

ext.outputChannel.show();
ext.outputChannel.appendLog(localize('schedulerEndpointCopiedMessage', 'Endpoint copied to clipboard: {0}', endpoint));
}
}
110 changes: 110 additions & 0 deletions src/commands/durableTaskScheduler/createScheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type ILocationWizardContext, type IResourceGroupWizardContext, LocationListStep, ResourceGroupCreateStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, createSubscriptionContext, type ExecuteActivityContext, type IActionContext, type ISubscriptionActionContext, subscriptionExperience } from "@microsoft/vscode-azext-utils";
import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
import { DurableTaskProvider, DurableTaskSchedulersResourceType } from "../../constants";
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { type DurableTaskSchedulerClient } from "../../tree/durableTaskScheduler/DurableTaskSchedulerClient";
import { type DurableTaskSchedulerDataBranchProvider } from "../../tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider";
import { createActivityContext } from "../../utils/activityUtils";
import { withCancellation } from "../../utils/cancellation";
import { type Progress } from "vscode";

interface ICreateSchedulerContext extends ISubscriptionActionContext, ILocationWizardContext, IResourceGroupWizardContext, ExecuteActivityContext {
subscription?: AzureSubscription;
schedulerName?: string;
}

class SchedulerNamingStep extends AzureWizardPromptStep<ICreateSchedulerContext> {
async prompt(wizardContext: ICreateSchedulerContext): Promise<void> {
wizardContext.schedulerName = await wizardContext.ui.showInputBox({
prompt: localize('schedulerNamingStepPrompt', 'Enter a name for the new scheduler')
})
}

shouldPrompt(wizardContext: ICreateSchedulerContext): boolean {
return !wizardContext.schedulerName;
}
}

class SchedulerCreationStep extends AzureWizardExecuteStep<ICreateSchedulerContext> {
priority: number = 1;

constructor(private readonly schedulerClient: DurableTaskSchedulerClient) {
super();
}

async execute(wizardContext: ICreateSchedulerContext, _: Progress<{ message?: string; increment?: number; }>): Promise<void> {
const location = await LocationListStep.getLocation(wizardContext);

const response = await this.schedulerClient.createScheduler(
wizardContext.subscription as AzureSubscription,
wizardContext.resourceGroup?.name as string,
location.name,
wizardContext.schedulerName as string
);

const status = await withCancellation(token => response.status.waitForCompletion(token), 1000 * 60 * 30);

if (status !== true) {
throw new Error(localize('schedulerCreationFailed', 'The scheduler could not be created.'));
}
}

shouldExecute(wizardContext: ICreateSchedulerContext): boolean {
return wizardContext.subscription !== undefined
&& wizardContext.resourceGroup !== undefined
&& wizardContext.schedulerName !== undefined;
}
}

export function createSchedulerCommandFactory(dataBranchProvider: DurableTaskSchedulerDataBranchProvider, schedulerClient: DurableTaskSchedulerClient) {
return async (actionContext: IActionContext, node?: { subscription: AzureSubscription }): Promise<void> => {
const subscription = node?.subscription ?? await subscriptionExperience(actionContext, ext.rgApiV2.resources.azureResourceTreeDataProvider);

const wizardContext: ICreateSchedulerContext =
{
subscription,

...actionContext,
...createSubscriptionContext(subscription),
...await createActivityContext()
};

const promptSteps: AzureWizardPromptStep<ICreateSchedulerContext>[] = [
new SchedulerNamingStep(),
new ResourceGroupListStep()
];

LocationListStep.addProviderForFiltering(wizardContext, DurableTaskProvider, DurableTaskSchedulersResourceType);
LocationListStep.addStep(wizardContext, promptSteps);

const wizard = new AzureWizard<ICreateSchedulerContext>(
wizardContext,
{
hideStepCount: true,
promptSteps,
executeSteps: [
Comment thread
philliphoff marked this conversation as resolved.
new ResourceGroupCreateStep(),
new SchedulerCreationStep(schedulerClient)
],
title: localize('createSchedulerWizardTitle', 'Create Durable Task Scheduler')
});

await wizard.prompt();

wizardContext.activityTitle = localize('createSchedulerActivityTitle', 'Create Durable Task Scheduler \'{0}\'', wizardContext.schedulerName);

try {
await wizard.execute();
}
finally {
dataBranchProvider.refresh();
}
}
}
Loading