Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for Durable Task Scheduler resources #4361

Merged
merged 13 commits into from
Jan 27, 2025
18 changes: 16 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"branches": [
{
"type": "FunctionApp"
},
{
"type": "DurableTaskScheduler"
}
]
},
Expand All @@ -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"
]
}
},
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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": [
Expand Down
4 changes: 3 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
30 changes: 30 additions & 0 deletions resources/durableTaskScheduler/DurableTaskScheduler.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/commands/durableTaskScheduler/openTaskHubDashboard.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
if (!taskHub) {
throw new Error(localize('noTaskHubSelectedErrorMessage', 'No task hub was selected.'));
}

await openUrl(taskHub?.dashboardUrl.toString(/* skipEncoding: */ true));
}
3 changes: 3 additions & 0 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
8 changes: 7 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<apiUtils.AzureExtensionApiProvider> {
ext.context = context;
Expand Down Expand Up @@ -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([<AzureFunctionsExtensionApi>{
Expand Down
65 changes: 65 additions & 0 deletions src/tree/durableTaskScheduler/DurableTaskHubResourceModel.ts
Original file line number Diff line number Diff line change
@@ -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<DurableTaskSchedulerModel[]>
{
return [];
}

getTreeItem(): TreeItem | Thenable<TreeItem>
{
const treeItem = new TreeItem(this.resource.name)

treeItem.iconPath = treeUtils.getIconPath('durableTaskScheduler/DurableTaskScheduler');
treeItem.contextValue = 'azFunc.dts.taskHub';

return treeItem;
}
}
69 changes: 69 additions & 0 deletions src/tree/durableTaskScheduler/DurableTaskSchedulerClient.ts
Original file line number Diff line number Diff line change
@@ -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<DurableTaskHubResource>;
getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise<DurableTaskHubResource[]>;
}

export class HttpDurableTaskSchedulerClient implements DurableTaskSchedulerClient {
async getSchedulerTaskHub(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string, taskHubName: string): Promise<DurableTaskHubResource> {
const taskHubsUrl = `${HttpDurableTaskSchedulerClient.getBaseUrl(subscription, resourceGroupName, schedulerName)}/taskHubs/${taskHubName}`;

const taskHub = await this.getAsJson<DurableTaskHubResource>(taskHubsUrl, subscription.authentication);

return taskHub;
}

async getSchedulerTaskHubs(subscription: AzureSubscription, resourceGroupName: string, schedulerName: string): Promise<DurableTaskHubResource[]> {
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<T>(url: string, authentication: AzureAuthentication): Promise<T> {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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<DurableTaskSchedulerModel> {
constructor(private readonly schedulerClient: DurableTaskSchedulerClient) {
}

getChildren(element: DurableTaskSchedulerModel): ProviderResult<DurableTaskSchedulerModel[]> {
return element.getChildren();
}

getResourceItem(element: AzureResource): DurableTaskSchedulerResourceModel | Thenable<DurableTaskSchedulerResourceModel> {
return new DurableTaskSchedulerResourceModel(element, this.schedulerClient);
}

getTreeItem(element: DurableTaskSchedulerModel): TreeItem | Thenable<TreeItem> {
return element.getTreeItem();
}
}
13 changes: 13 additions & 0 deletions src/tree/durableTaskScheduler/DurableTaskSchedulerModel.ts
Original file line number Diff line number Diff line change
@@ -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<DurableTaskSchedulerModel[]>;

getTreeItem(): TreeItem | Thenable<TreeItem>;
}
Loading
Loading