mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Add URI handler for resource deployment (#14470)
* Add URL handler for resource deployment * Add tests
This commit is contained in:
@@ -14,6 +14,7 @@ import { DeploymentInputDialog } from './ui/deploymentInputDialog';
|
|||||||
import { ResourceTypePickerDialog } from './ui/resourceTypePickerDialog';
|
import { ResourceTypePickerDialog } from './ui/resourceTypePickerDialog';
|
||||||
import * as rd from 'resource-deployment';
|
import * as rd from 'resource-deployment';
|
||||||
import { getExtensionApi } from './api';
|
import { getExtensionApi } from './api';
|
||||||
|
import { UriHandlerService } from './services/uriHandlerService';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -31,6 +32,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<rd.IEx
|
|||||||
validationFailures.forEach(message => console.error(message));
|
validationFailures.forEach(message => console.error(message));
|
||||||
return <any>undefined;
|
return <any>undefined;
|
||||||
}
|
}
|
||||||
|
const uriHandlerService = new UriHandlerService(resourceTypeService);
|
||||||
|
vscode.window.registerUriHandler(uriHandlerService);
|
||||||
/**
|
/**
|
||||||
* Opens a new ResourceTypePickerDialog
|
* Opens a new ResourceTypePickerDialog
|
||||||
* @param defaultResourceTypeName The resource type name to have selected by default
|
* @param defaultResourceTypeName The resource type name to have selected by default
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { ResourceTypeService } from './resourceTypeService';
|
||||||
|
|
||||||
|
export class UriHandlerService implements vscode.UriHandler {
|
||||||
|
constructor(private _resourceTypeService: ResourceTypeService) { }
|
||||||
|
|
||||||
|
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
|
||||||
|
// Path to start a deployment
|
||||||
|
// Supported URI parameters :
|
||||||
|
// - type (optional) : The resource type to start the deployment for
|
||||||
|
// - params (optional) : A JSON blob of variable names/values to pass as initial values to the wizard. Note
|
||||||
|
// that the JSON blob must be URI-encoded in order to be properly handled
|
||||||
|
// Example URIs :
|
||||||
|
// azuredatastudio://Microsoft.resource-deployment/deploy
|
||||||
|
// azuredatastudio://Microsoft.resource-deployment/deploy?type=arc-controller
|
||||||
|
// azuredatastudio://Microsoft.resource-deployment/deploy?type=arc-controller¶ms=%7B%22AZDATA_NB_VAR_ARC_SUBSCRIPTION%22%3A%22abdcef12-3456-7890-abcd-ef1234567890%22%2C%22AZDATA_NB_VAR_ARC_RESOURCE_GROUP%22%3A%22my-rg%22%2C%22AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION%22%3A%22westus%22%2C%22AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME%22%3A%22arc-dc%22%7D
|
||||||
|
if (uri.path === '/deploy') {
|
||||||
|
const params = uri.query.split('&').map(kv => kv.split('='));
|
||||||
|
const paramType = params.find(param => param[0] === 'type')?.[1];
|
||||||
|
const wizardParams = JSON.parse(params.find(param => param[0] === 'params')?.[1] ?? '{}');
|
||||||
|
|
||||||
|
const resourceType = this._resourceTypeService.getResourceTypes().find(type => type.name === paramType);
|
||||||
|
if (paramType && !resourceType) {
|
||||||
|
console.warn(`Unknown resource type ${paramType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceType) {
|
||||||
|
this._resourceTypeService.startDeployment(resourceType, undefined, wizardParams);
|
||||||
|
} else {
|
||||||
|
return vscode.commands.executeCommand('azdata.resource.deploy');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { apiService } from '../../services/apiService';
|
import { apiService } from '../../services/apiService';
|
||||||
import assert = require('assert');
|
import * as assert from 'assert';
|
||||||
|
|
||||||
describe('API Service Tests', function (): void {
|
describe('API Service Tests', function (): void {
|
||||||
it('get azurecoreApi returns azure api', () => {
|
it('get azurecoreApi returns azure api', () => {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import assert = require('assert');
|
import * as assert from 'assert';
|
||||||
import should = require('should');
|
import * as should from 'should';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { ResourceTypeService, processWhenClause } from '../../services/resourceTypeService';
|
import { ResourceTypeService, processWhenClause } from '../../services/resourceTypeService';
|
||||||
import { IPlatformService } from '../../services/platformService';
|
import { IPlatformService } from '../../services/platformService';
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import 'mocha';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { ResourceTypeService } from '../../services/resourceTypeService';
|
||||||
|
import { UriHandlerService } from '../../services/uriHandlerService';
|
||||||
|
import { ResourceType } from '../../interfaces';
|
||||||
|
|
||||||
|
const resourceType1Name = 'resource-type-1';
|
||||||
|
|
||||||
|
const mockResourceTypes: ResourceType[] = [
|
||||||
|
{
|
||||||
|
name: resourceType1Name,
|
||||||
|
displayName: 'Resource Type 1',
|
||||||
|
description: '',
|
||||||
|
platforms: '*',
|
||||||
|
icon: '',
|
||||||
|
options: [],
|
||||||
|
providers: [],
|
||||||
|
helpTexts: [],
|
||||||
|
getOkButtonText: (selectedOptions: { option: string, value: string }[]) => undefined,
|
||||||
|
getProvider: (selectedOptions: { option: string, value: string }[]) => undefined,
|
||||||
|
getAgreementInfo: (selectedOptions: { option: string, value: string }[]) => undefined,
|
||||||
|
getHelpText: (selectedOption: { option: string, value: string }[]) => undefined
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('uriHandlerService Tests', function (): void {
|
||||||
|
|
||||||
|
afterEach(function (): void {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const resourceTypeServiceMock = TypeMoq.Mock.ofType<ResourceTypeService>();
|
||||||
|
resourceTypeServiceMock.setup(x => x.getResourceTypes()).returns(() => {
|
||||||
|
return mockResourceTypes;
|
||||||
|
});
|
||||||
|
const uriHandlerService = new UriHandlerService(resourceTypeServiceMock.object);
|
||||||
|
|
||||||
|
it('unknown path is ignored', async function (): Promise<void> {
|
||||||
|
const uri = vscode.Uri.parse('azuredatastudio://Microsoft.resource-deployment/badPath');
|
||||||
|
await uriHandlerService.handleUri(uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deploy path', function (): void {
|
||||||
|
it('no parameters', async function (): Promise<void> {
|
||||||
|
const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand');
|
||||||
|
const uri = vscode.Uri.parse('azuredatastudio://Microsoft.resource-deployment/deploy');
|
||||||
|
await uriHandlerService.handleUri(uri);
|
||||||
|
sinon.assert.calledOnce(executeCommandStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unknown type', async function (): Promise<void> {
|
||||||
|
const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand');
|
||||||
|
const uri = vscode.Uri.parse('azuredatastudio://Microsoft.resource-deployment/deploy?type=unknown-type');
|
||||||
|
await uriHandlerService.handleUri(uri);
|
||||||
|
sinon.assert.calledOnce(executeCommandStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with type only', async function (): Promise<void> {
|
||||||
|
const uri = vscode.Uri.parse(`azuredatastudio://Microsoft.resource-deployment/deploy?type=${resourceType1Name}`);
|
||||||
|
await uriHandlerService.handleUri(uri);
|
||||||
|
resourceTypeServiceMock.verify(x => x.startDeployment(TypeMoq.It.isObjectWith(mockResourceTypes[0]), undefined, TypeMoq.It.isObjectWith({})), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with parameters', async function (): Promise<void> {
|
||||||
|
const params = { 'param1': 'value1', 'param2': 'value2' };
|
||||||
|
const uri = vscode.Uri.parse(`azuredatastudio://Microsoft.resource-deployment/deploy?type=${resourceType1Name}¶ms=${encodeURIComponent(JSON.stringify(params))}`);
|
||||||
|
await uriHandlerService.handleUri(uri);
|
||||||
|
resourceTypeServiceMock.verify(x => x.startDeployment(TypeMoq.It.isObjectWith(mockResourceTypes[0]), undefined, TypeMoq.It.isObjectWith(params)), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1062,7 +1062,7 @@ async function processAzureAccountField(context: AzureAccountFieldContext): Prom
|
|||||||
|
|
||||||
// Check if we have an initial subscription value - if we do then the user isn't going to be allowed to change any of the
|
// Check if we have an initial subscription value - if we do then the user isn't going to be allowed to change any of the
|
||||||
// Azure values so we can skip adding the account picker
|
// Azure values so we can skip adding the account picker
|
||||||
const hasInitialSubscriptionValue = !!context.initialVariableValues?.[context.fieldInfo.subscriptionVariableName || ''].toString();
|
const hasInitialSubscriptionValue = !!context.initialVariableValues?.[context.fieldInfo.subscriptionVariableName || '']?.toString();
|
||||||
if (!hasInitialSubscriptionValue) {
|
if (!hasInitialSubscriptionValue) {
|
||||||
accountComponents = createAzureAccountDropdown(context);
|
accountComponents = createAzureAccountDropdown(context);
|
||||||
accountDropdown = accountComponents.accountDropdown;
|
accountDropdown = accountComponents.accountDropdown;
|
||||||
@@ -1215,7 +1215,7 @@ function createAzureSubscriptionComponent(
|
|||||||
cssStyles: context.fieldInfo.labelCSSStyles
|
cssStyles: context.fieldInfo.labelCSSStyles
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultValue = context.initialVariableValues?.[context.fieldInfo.subscriptionVariableName || ''].toString() ?? (context.fieldInfo.required ? undefined : '');
|
const defaultValue = context.initialVariableValues?.[context.fieldInfo.subscriptionVariableName || '']?.toString() ?? (context.fieldInfo.required ? undefined : '');
|
||||||
let subscriptionInputInfo: InputComponentInfo<AzureComponent>;
|
let subscriptionInputInfo: InputComponentInfo<AzureComponent>;
|
||||||
let setValueFunc: (value: InputValueType) => void;
|
let setValueFunc: (value: InputValueType) => void;
|
||||||
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
||||||
@@ -1373,7 +1373,7 @@ function createAzureResourceGroupsComponent(
|
|||||||
width: context.fieldInfo.labelWidth,
|
width: context.fieldInfo.labelWidth,
|
||||||
cssStyles: context.fieldInfo.labelCSSStyles
|
cssStyles: context.fieldInfo.labelCSSStyles
|
||||||
});
|
});
|
||||||
const defaultValue = context.initialVariableValues?.[context.fieldInfo.resourceGroupVariableName || ''].toString() ?? (context.fieldInfo.required ? undefined : '');
|
const defaultValue = context.initialVariableValues?.[context.fieldInfo.resourceGroupVariableName || '']?.toString() ?? (context.fieldInfo.required ? undefined : '');
|
||||||
let resourceGroupInputInfo: InputComponentInfo<AzureComponent>;
|
let resourceGroupInputInfo: InputComponentInfo<AzureComponent>;
|
||||||
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
@@ -1478,7 +1478,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
|||||||
width: context.fieldInfo.labelWidth,
|
width: context.fieldInfo.labelWidth,
|
||||||
cssStyles: context.fieldInfo.labelCSSStyles
|
cssStyles: context.fieldInfo.labelCSSStyles
|
||||||
});
|
});
|
||||||
const defaultValue = context.initialVariableValues?.[context.fieldInfo.locationVariableName || ''].toString() ?? (context.fieldInfo.required ? undefined : '');
|
const defaultValue = context.initialVariableValues?.[context.fieldInfo.locationVariableName || '']?.toString() ?? (context.fieldInfo.required ? undefined : '');
|
||||||
let locationInputInfo: InputComponentInfo<AzureComponent>;
|
let locationInputInfo: InputComponentInfo<AzureComponent>;
|
||||||
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
// If we have an default value then we don't allow users to modify it - so use a disabled text input box instead
|
||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
|
|||||||
Reference in New Issue
Block a user