diff --git a/package.json b/package.json
index 20802fdf1..d38302501 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,9 @@
"branches": [
{
"type": "FunctionApp"
+ },
+ {
+ "type": "DurableTaskScheduler"
}
]
},
@@ -69,10 +72,12 @@
],
"activation": {
"onFetch": [
- "microsoft.web/sites"
+ "microsoft.web/sites",
+ "microsoft.durabletask/schedulers"
],
"onResolve": [
- "microsoft.web/sites"
+ "microsoft.web/sites",
+ "microsoft.durabletask/schedulers"
]
}
},
@@ -369,6 +374,11 @@
"title": "%azureFunctions.eventGrid.sendMockRequest%",
"category": "Azure Functions",
"icon": "$(notebook-execute)"
+ },
+ {
+ "command": "azureFunctions.durableTaskScheduler.openTaskHubDashboard",
+ "title": "%azureFunctions.durableTaskScheduler.openTaskHubDashboard%",
+ "category": "Azure Functions"
}
],
"submenus": [
@@ -662,6 +672,10 @@
"command": "azureResourceGroups.refresh",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.*folder/",
"group": "1@1"
+ },
+ {
+ "command": "azureFunctions.durableTaskScheduler.openTaskHubDashboard",
+ "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /azFunc.dts.taskHub/"
}
],
"explorer/context": [
diff --git a/package.nls.json b/package.nls.json
index d8d5b78a0..15709f06a 100644
--- a/package.nls.json
+++ b/package.nls.json
@@ -118,5 +118,7 @@
"azureFunctions.walkthrough.functionsStart.initialize.title": "Initialize an existing project",
"azureFunctions.walkthrough.functionsStart.scenarios.description": "Learn how you can use Azure Functions to build event-driven systems.\n\nIf you're just getting started with Azure Functions, you can [learn about the anatomy of an Azure Functions application](https://aka.ms/functions-getstarted-devguide).",
"azureFunctions.walkthrough.functionsStart.scenarios.title": "Explore common scenarios",
- "azureFunctions.walkthrough.functionsStart.title": "Get Started with Azure Functions"
+ "azureFunctions.walkthrough.functionsStart.title": "Get Started with Azure Functions",
+
+ "azureFunctions.durableTaskScheduler.openTaskHubDashboard": "Open in Dashboard"
}
diff --git a/resources/durableTaskScheduler/DurableTaskScheduler.svg b/resources/durableTaskScheduler/DurableTaskScheduler.svg
new file mode 100644
index 000000000..6a5efe2f4
--- /dev/null
+++ b/resources/durableTaskScheduler/DurableTaskScheduler.svg
@@ -0,0 +1,30 @@
+
diff --git a/src/commands/durableTaskScheduler/openTaskHubDashboard.ts b/src/commands/durableTaskScheduler/openTaskHubDashboard.ts
new file mode 100644
index 000000000..99b0fc1ba
--- /dev/null
+++ b/src/commands/durableTaskScheduler/openTaskHubDashboard.ts
@@ -0,0 +1,16 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { openUrl, type IActionContext } from "@microsoft/vscode-azext-utils";
+import { type DurableTaskHubResourceModel } from "../../tree/durableTaskScheduler/DurableTaskHubResourceModel";
+import { localize } from '../../localize';
+
+export async function openTaskHubDashboard(_: IActionContext, taskHub: DurableTaskHubResourceModel | undefined): Promise {
+ if (!taskHub) {
+ throw new Error(localize('noTaskHubSelectedErrorMessage', 'No task hub was selected.'));
+ }
+
+ await openUrl(taskHub?.dashboardUrl.toString(/* skipEncoding: */ true));
+}
diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts
index 2befbc369..0232d85cc 100644
--- a/src/commands/registerCommands.ts
+++ b/src/commands/registerCommands.ts
@@ -63,6 +63,7 @@ import { stopFunctionApp } from './stopFunctionApp';
import { swapSlot } from './swapSlot';
import { disableFunction, enableFunction } from './updateDisabledState';
import { viewProperties } from './viewProperties';
+import { openTaskHubDashboard } from './durableTaskScheduler/openTaskHubDashboard';
export function registerCommands(): void {
commands.registerCommand('azureFunctions.agent.getCommands', getCommands);
@@ -154,4 +155,6 @@ export function registerCommands(): void {
ext.eventGridProvider = new EventGridCodeLensProvider();
ext.context.subscriptions.push(languages.registerCodeLensProvider({ pattern: '**/*.eventgrid.json' }, ext.eventGridProvider));
registerCommand('azureFunctions.eventGrid.sendMockRequest', sendEventGridRequest);
+
+ registerCommandWithTreeNodeUnwrapping('azureFunctions.durableTaskScheduler.openTaskHubDashboard', openTaskHubDashboard);
}
diff --git a/src/extension.ts b/src/extension.ts
index 523ce3b13..fa0a35d2d 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -8,7 +8,7 @@
import { registerAppServiceExtensionVariables } from '@microsoft/vscode-azext-azureappservice';
import { registerAzureUtilsExtensionVariables, type AzureAccountTreeItemBase } from '@microsoft/vscode-azext-azureutils';
import { callWithTelemetryAndErrorHandling, createApiProvider, createAzExtOutputChannel, createExperimentationService, registerErrorHandler, registerEvent, registerReportIssueCommand, registerUIExtensionVariables, type IActionContext, type apiUtils } from '@microsoft/vscode-azext-utils';
-import { AzExtResourceType } from '@microsoft/vscode-azureresources-api';
+import { AzExtResourceType, getAzureResourcesExtensionApi } from '@microsoft/vscode-azureresources-api';
import * as vscode from 'vscode';
import { FunctionAppResolver } from './FunctionAppResolver';
import { FunctionsLocalResourceProvider } from './LocalResourceProvider';
@@ -38,6 +38,8 @@ import { verifyVSCodeConfigOnActivate } from './vsCodeConfig/verifyVSCodeConfigO
import { type AzureFunctionsExtensionApi } from './vscode-azurefunctions.api';
import { listLocalFunctions } from './workspace/listLocalFunctions';
import { listLocalProjects } from './workspace/listLocalProjects';
+import { DurableTaskSchedulerDataBranchProvider } from './tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider';
+import { HttpDurableTaskSchedulerClient } from './tree/durableTaskScheduler/DurableTaskSchedulerClient';
export async function activateInternal(context: vscode.ExtensionContext, perfStats: { loadStartTime: number; loadEndTime: number }, ignoreBundle?: boolean): Promise {
ext.context = context;
@@ -104,6 +106,10 @@ export async function activateInternal(context: vscode.ExtensionContext, perfSta
ext.azureAccountTreeItem = ext.rgApi.appResourceTree._rootTreeItem as AzureAccountTreeItemBase;
ext.rgApi.registerApplicationResourceResolver(AzExtResourceType.FunctionApp, new FunctionAppResolver());
ext.rgApi.registerWorkspaceResourceProvider('func', new FunctionsLocalResourceProvider());
+
+ const azureResourcesApi = await getAzureResourcesExtensionApi(context, '2.0.0');
+
+ azureResourcesApi.resources.registerAzureResourceBranchDataProvider('DurableTaskScheduler' as AzExtResourceType, new DurableTaskSchedulerDataBranchProvider(new HttpDurableTaskSchedulerClient()));
});
return createApiProvider([{
diff --git a/src/tree/durableTaskScheduler/DurableTaskHubResourceModel.ts b/src/tree/durableTaskScheduler/DurableTaskHubResourceModel.ts
new file mode 100644
index 000000000..dd3bd1290
--- /dev/null
+++ b/src/tree/durableTaskScheduler/DurableTaskHubResourceModel.ts
@@ -0,0 +1,65 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { type AzureResource, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
+import { type DurableTaskSchedulerModel } from "./DurableTaskSchedulerModel";
+import { type DurableTaskHubResource, type DurableTaskSchedulerClient } from "./DurableTaskSchedulerClient";
+import { type ProviderResult, TreeItem, Uri } from "vscode";
+import { treeUtils } from "../../utils/treeUtils";
+import { localize } from '../../localize';
+
+export class DurableTaskHubResourceModel implements DurableTaskSchedulerModel {
+ constructor(
+ private readonly schedulerResource: AzureResource,
+ private readonly resource: DurableTaskHubResource,
+ private readonly schedulerClient: DurableTaskSchedulerClient) {
+ }
+
+ public get azureResourceId() { return this.resource.id; }
+
+ get dashboardUrl(): Uri { return Uri.parse(this.resource.properties.dashboardUrl); }
+
+ get id(): string { return this.resource.id; }
+
+ get portalUrl(): Uri {
+ const url: string = `${this.schedulerResource.subscription.environment.portalUrl}/#@${this.schedulerResource.subscription.tenantId}/resource${this.id}`;
+
+ return Uri.parse(url);
+ }
+
+ get viewProperties(): ViewPropertiesModel {
+ return {
+ label: this.resource.name,
+ getData: async () => {
+ if (!this.schedulerResource.resourceGroup) {
+ throw new Error(localize('noResourceGroupErrorMessage', 'Azure resource does not have a valid resource group name.'));
+ }
+
+ const json = await this.schedulerClient.getSchedulerTaskHub(
+ this.schedulerResource.subscription,
+ this.schedulerResource.resourceGroup,
+ this.schedulerResource.name,
+ this.resource.name);
+
+ return json;
+ }
+ };
+ }
+
+ getChildren(): ProviderResult
+ {
+ return [];
+ }
+
+ getTreeItem(): TreeItem | Thenable
+ {
+ const treeItem = new TreeItem(this.resource.name)
+
+ treeItem.iconPath = treeUtils.getIconPath('durableTaskScheduler/DurableTaskScheduler');
+ treeItem.contextValue = 'azFunc.dts.taskHub';
+
+ return treeItem;
+ }
+}
diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts
new file mode 100644
index 000000000..d37318fc8
--- /dev/null
+++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts
@@ -0,0 +1,69 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { type AzureAuthentication, type AzureSubscription } from "@microsoft/vscode-azureresources-api";
+import { localize } from '../../localize';
+
+export interface DurableTaskHubResource {
+ readonly id: string;
+ readonly name: string;
+ readonly properties: {
+ readonly dashboardUrl: string;
+ };
+}
+
+export interface DurableTaskSchedulerClient {
+ getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise;
+ getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise;
+}
+
+export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClient {
+ async getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise {
+ const taskHubsUrl = `${HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName, schedulerName)}/taskHubs/${taskHubName}`;
+
+ const taskHub = await this.getAsJson(taskHubsUrl, subscription.authentication);
+
+ return taskHub;
+ }
+
+ async getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise {
+ const taskHubsUrl = `${HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName, schedulerName)}/taskHubs`;
+
+ const response = await this.getAsJson<{ value: DurableTaskHubResource[] }>(taskHubsUrl, subscription.authentication);
+
+ return response.value;
+ }
+
+ private static getBaseUrl(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string) {
+ const provider = 'Microsoft.DurableTask';
+
+ return `${subscription.environment.resourceManagerEndpointUrl}/subscriptions/${subscription.subscriptionId}/resourceGroups/${resourceGroupName}/providers/${provider}/schedulers/${schedulerName}`;
+ }
+
+ private async getAsJson(url: string, authentication: AzureAuthentication): Promise {
+ const apiVersion = '2024-10-01-preview';
+ const versionedUrl = `${url}?api-version=${apiVersion}`;
+
+ const authSession = await authentication.getSession();
+
+ if (!authSession) {
+ throw new Error(localize('noAuthenticationSessionErrorMessage', 'Unable to obtain an authentication session.'));
+ }
+
+ const accessToken = authSession.accessToken;
+
+ const request = new Request(versionedUrl);
+
+ request.headers.append('Authorization', `Bearer ${accessToken}`);
+
+ const response = await fetch(request);
+
+ if (!response.ok) {
+ throw new Error(localize('failureInvokingArmErrorMessage', 'Azure management API returned an unsuccessful response.'));
+ }
+
+ return await response.json() as T;
+ }
+}
diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider.ts
new file mode 100644
index 000000000..742bc299e
--- /dev/null
+++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerDataBranchProvider.ts
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { type AzureResource, type AzureResourceBranchDataProvider } from "@microsoft/vscode-azureresources-api";
+import { type ProviderResult, type TreeItem } from "vscode";
+import { type DurableTaskSchedulerClient } from "./DurableTaskSchedulerClient";
+import { type DurableTaskSchedulerModel } from "./DurableTaskSchedulerModel";
+import { DurableTaskSchedulerResourceModel } from "./DurableTaskSchedulerResourceModel";
+
+export class DurableTaskSchedulerDataBranchProvider implements AzureResourceBranchDataProvider {
+ constructor(private readonly schedulerClient: DurableTaskSchedulerClient) {
+ }
+
+ getChildren(element: DurableTaskSchedulerModel): ProviderResult {
+ return element.getChildren();
+ }
+
+ getResourceItem(element: AzureResource): DurableTaskSchedulerResourceModel | Thenable {
+ return new DurableTaskSchedulerResourceModel(element, this.schedulerClient);
+ }
+
+ getTreeItem(element: DurableTaskSchedulerModel): TreeItem | Thenable {
+ return element.getTreeItem();
+ }
+}
diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerModel.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerModel.ts
new file mode 100644
index 000000000..48ab2cf2a
--- /dev/null
+++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerModel.ts
@@ -0,0 +1,13 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { type AzureResourceModel } from "@microsoft/vscode-azureresources-api";
+import { type ProviderResult, type TreeItem } from "vscode";
+
+export interface DurableTaskSchedulerModel extends AzureResourceModel {
+ getChildren(): ProviderResult;
+
+ getTreeItem(): TreeItem | Thenable;
+}
diff --git a/src/tree/durableTaskScheduler/DurableTaskSchedulerResourceModel.ts b/src/tree/durableTaskScheduler/DurableTaskSchedulerResourceModel.ts
new file mode 100644
index 000000000..463a485b5
--- /dev/null
+++ b/src/tree/durableTaskScheduler/DurableTaskSchedulerResourceModel.ts
@@ -0,0 +1,36 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { type AzureResource, type AzureResourceModel } from "@microsoft/vscode-azureresources-api";
+import { type DurableTaskSchedulerModel } from "./DurableTaskSchedulerModel";
+import { type DurableTaskSchedulerClient } from "./DurableTaskSchedulerClient";
+import { DurableTaskHubResourceModel } from "./DurableTaskHubResourceModel";
+import { TreeItem, TreeItemCollapsibleState } from "vscode";
+import { localize } from '../../localize';
+
+export class DurableTaskSchedulerResourceModel implements DurableTaskSchedulerModel, AzureResourceModel {
+ public constructor(private readonly resource: AzureResource, private readonly schedulerClient: DurableTaskSchedulerClient) {
+ }
+
+ async getChildren(): Promise {
+ if (!this.resource.resourceGroup) {
+ throw new Error(localize('noResourceGroupErrorMessage', 'Azure resource does not have a valid resource group name.'));
+ }
+
+ const taskHubs = await this.schedulerClient.getSchedulerTaskHubs(this.resource.subscription, this.resource.resourceGroup, this.resource.name);
+
+ return taskHubs.map(resource => new DurableTaskHubResourceModel(this.resource, resource, this.schedulerClient));
+ }
+
+ getTreeItem(): TreeItem | Thenable {
+ return new TreeItem(this.name, TreeItemCollapsibleState.Collapsed);
+ }
+
+ public get id(): string | undefined { return this.resource.id; }
+
+ public get azureResourceId() { return this.resource.id; }
+
+ public get name() { return this.resource.name; }
+}