Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
efa7e82
first working poc
hossam-nasr Jan 19, 2024
f8f4558
prompt for fields enclosed by {}
hossam-nasr Jan 19, 2024
9995b87
add button and code lens + clean up and refactor
hossam-nasr Feb 20, 2024
6c48f01
fix rebase
hossam-nasr Feb 20, 2024
9fa9774
more refactor and cleanup
hossam-nasr Feb 20, 2024
8c646aa
fix change in tree
hossam-nasr Feb 20, 2024
7c77ae7
add license comments
hossam-nasr Feb 20, 2024
20cc9e6
import type
hossam-nasr Feb 20, 2024
3a1425d
restructure
hossam-nasr Feb 29, 2024
00089ec
switch to using a wizard
hossam-nasr Feb 29, 2024
1ea43c6
keep the file open and remember the function associated with each file
hossam-nasr Feb 29, 2024
2adbf39
add aka.ms link & add code lens at the bottom
hossam-nasr Feb 29, 2024
f043928
switch to using AzExtFsExtra
hossam-nasr Feb 29, 2024
e0a3cb0
wrap in try/finally
hossam-nasr Feb 29, 2024
012a593
rename to save and execute
hossam-nasr Feb 29, 2024
8ee991e
revert aka.ms link for now
hossam-nasr Feb 29, 2024
203c151
fix codelens last line
hossam-nasr Feb 29, 2024
9b2eeb7
remove commented out code
hossam-nasr Mar 5, 2024
acd4317
add source of event sources
hossam-nasr Mar 5, 2024
79ea4f3
revert styling changes in unrelated file
hossam-nasr Mar 5, 2024
b2029b4
remove last line codelens
hossam-nasr Mar 5, 2024
1ceca9e
add licenses
hossam-nasr Mar 5, 2024
4eb5e40
PR nits
hossam-nasr Mar 5, 2024
b17f5e8
show info box only once per session
hossam-nasr Mar 5, 2024
907ce44
update to use workspaceState
hossam-nasr Mar 5, 2024
3a93fe6
PR feedback
hossam-nasr Mar 9, 2024
3c055fd
remove weird defaulting
hossam-nasr Mar 9, 2024
bdf6a59
telemetry: entry point
hossam-nasr Mar 9, 2024
b24e300
telemetry: event source & type
hossam-nasr Mar 9, 2024
774e414
telemetry: whether file was modified
hossam-nasr Mar 9, 2024
4241096
check
hossam-nasr Mar 9, 2024
99333f8
use constants
hossam-nasr Mar 13, 2024
e90ead8
Merge branch 'main' into hossamnasr/eventgrid-lde
hossam-nasr Mar 13, 2024
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
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@
"command": "azureFunctions.viewProperties",
"title": "%azureFunctions.viewProperties%",
"category": "Azure Functions"
},
{
"command": "azureFunctions.eventGrid.sendMockRequest",
Comment thread
hossam-nasr marked this conversation as resolved.
"title": "%azureFunctions.eventGrid.sendMockRequest%",
"category": "Azure Functions",
"icon": "$(notebook-execute)"
}
],
"submenus": [
Expand Down Expand Up @@ -687,6 +693,13 @@
"when": "resourceFilename==function.json",
"group": "zzz_binding@1"
}
],
"editor/title": [
{
"command": "azureFunctions.eventGrid.sendMockRequest",
"when": "resourceFilename=~/.*.eventgrid.json$/",
Comment thread
hossam-nasr marked this conversation as resolved.
"group": "navigation@1"
}
]
},
"jsonValidation": [
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"azureFunctions.viewCommitInGitHub": "View Commit in GitHub",
"azureFunctions.viewDeploymentLogs": "View Deployment Logs",
"azureFunctions.viewProperties": "View Properties",
"azureFunctions.eventGrid.sendMockRequest": "Save and execute...",
"azureFunctions.walkthrough.functionsStart.create.description": "If you're just getting started, you will need to create an Azure Functions project. Follow along with the [Visual Studio Code developer guide](https://aka.ms/functions-getstarted-vscode) for step-by-step instructions.\n[Create New Project](command:azureFunctions.createNewProject)",
"azureFunctions.walkthrough.functionsStart.create.title": "Create a new Azure Functions project",
"azureFunctions.walkthrough.functionsStart.description": "Learn about Azure Functions and the Azure Functions extension for Visual Studio Code",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CodeLens, Range, type CodeLensProvider } from 'vscode';
import { localize } from '../../../localize';

export class EventGridCodeLensProvider implements CodeLensProvider {
public provideCodeLenses(): CodeLens[] {
const firstLineLens = new CodeLens(new Range(0, 0, 0, 0));

firstLineLens.command = {
title: localize('saveExecute', 'Save and execute'),
command: 'azureFunctions.eventGrid.sendMockRequest',
};

return [firstLineLens];
}
}
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 { type IActionContext } from "@microsoft/vscode-azext-utils";
import { type EventGridSource } from "./eventGridSources";


Comment thread
hossam-nasr marked this conversation as resolved.
Outdated
export interface EventGridExecuteFunctionContext extends IActionContext {
eventSource?: EventGridSource;
selectedFileName?: string;
selectedFileUrl?: string;
fileOpened?: boolean;
}

95 changes: 95 additions & 0 deletions src/commands/executeFunction/eventGrid/EventGridFileOpenStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtFsExtra, AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
import * as os from 'os';
import * as path from "path";
import * as vscode from 'vscode';
import { type Progress } from "vscode";
import { ext } from "../../../extensionVariables";
import { localize } from "../../../localize";
import { feedUtils } from "../../../utils/feedUtils";
import { type EventGridExecuteFunctionContext } from "./EventGridExecuteFunctionContext";

export class EventGridFileOpenStep extends AzureWizardExecuteStep<EventGridExecuteFunctionContext> {
public priority: number;

public async execute(context: EventGridExecuteFunctionContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined; }>): Promise<void> {
const eventSource = nonNullProp(context, 'eventSource');
const selectedFileName = nonNullProp(context, 'selectedFileName');
const selectedFileUrl = nonNullProp(context, 'selectedFileUrl');

// Get selected contents of sample request
const downloadingMsg: string = localize('downloadingSample', 'Downloading sample request...');
progress.report({ message: downloadingMsg });
const selectedFileContent = await feedUtils.getJsonFeed(context, selectedFileUrl);

// Create a temp file with the sample request & open in new window
const openingFileMsg: string = localize('openingFile', 'Opening file...');
progress.report({ message: openingFileMsg });
const tempFilePath: string = await createTempSampleFile(eventSource, selectedFileName, selectedFileContent);
const document: vscode.TextDocument = await vscode.workspace.openTextDocument(tempFilePath);
await vscode.window.showTextDocument(document, {
preview: false,
});
ext.fileToFunctionNodeMap.set(document.fileName, nonNullProp(ext, 'currentExecutingFunctionNode'));
context.fileOpened = true;

// Request will be sent when the user clicks on the button or on the codelens link
// Show the message only once per workspace
if (!ext.context.workspaceState.get('didShowEventGridFileOpenMsg')) {
const doneMsg = localize('modifyFile', "You can modify the file and then click the 'Save and execute' button to send the request.");
void vscode.window.showInformationMessage(doneMsg);
await ext.context.workspaceState.update('didShowEventGridFileOpenMsg', true);
}

Comment thread
hossam-nasr marked this conversation as resolved.

// Set a listener to delete the temp file after it's closed
void new Promise<void>((resolve, reject) => {
const disposable = vscode.workspace.onDidCloseTextDocument(async (closedDocument) => {
if (closedDocument.fileName === document.fileName) {
try {
ext.fileToFunctionNodeMap.delete(document.fileName);
await AzExtFsExtra.deleteResource(tempFilePath);
resolve();
} catch (error) {
reject(error);
Comment thread
hossam-nasr marked this conversation as resolved.
} finally {
disposable.dispose();
}
}
});
});
}

public shouldExecute(context: EventGridExecuteFunctionContext): boolean {
return !context.fileOpened
}

}

async function createTempSampleFile(eventSource: string, fileName: string, contents: {}): Promise<string> {
const samplesDirPath = await getSamplesDirPath(eventSource);
const sampleFileName = fileName.replace(/\.json$/, '.eventgrid.json');
const filePath: string = path.join(samplesDirPath, sampleFileName);

await AzExtFsExtra.writeJSON(filePath, contents);

return filePath;
}

async function getSamplesDirPath(eventSource: string): Promise<string> {
const baseDir: string = path.join(os.tmpdir(), 'vscode', 'azureFunctions', 'eventGridSamples');

// Create the path to the directory
const dirPath = path.join(baseDir, eventSource);

// Create the directory if it doesn't already exist
await AzExtFsExtra.ensureDir(dirPath);

// Return the path to the directory
return dirPath;
}

36 changes: 36 additions & 0 deletions src/commands/executeFunction/eventGrid/EventGridSourceStep.ts
Original file line number Diff line number Diff line change
@@ -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 { AzureWizardPromptStep, type IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
import { localize } from "../../../localize";
import { type EventGridExecuteFunctionContext } from "./EventGridExecuteFunctionContext";
import { supportedEventGridSourceLabels, supportedEventGridSources, type EventGridSource } from "./eventGridSources";

export class EventGridSourceStep extends AzureWizardPromptStep<EventGridExecuteFunctionContext> {
public hideStepCount: boolean = false;

public async prompt(context: EventGridExecuteFunctionContext): Promise<void> {
// Prompt for event source
const eventGridSourcePicks: IAzureQuickPickItem<EventGridSource | undefined>[] = supportedEventGridSources.map((source: EventGridSource) => {
return {
label: supportedEventGridSourceLabels.get(source) || source,
data: source,
};
});
const eventSource: EventGridSource =
(
await context.ui.showQuickPick(eventGridSourcePicks, {
placeHolder: localize('selectEventSource', 'Select the event source'),
stepName: 'eventGridSource',
})
).data ?? 'Microsoft.Storage';
Comment thread
hossam-nasr marked this conversation as resolved.
Outdated

context.eventSource = eventSource;
}

public shouldPrompt(context: EventGridExecuteFunctionContext): boolean {
return !context.eventSource;
}
}
72 changes: 72 additions & 0 deletions src/commands/executeFunction/eventGrid/EventGridTypeStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep, type IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
import { localize } from "../../../localize";
import { feedUtils } from "../../../utils/feedUtils";
import { type EventGridExecuteFunctionContext } from "./EventGridExecuteFunctionContext";

const sampleFilesUrl =
'https://api.github.com/repos/Azure/azure-rest-api-specs/contents/specification/eventgrid/data-plane/' +
'{eventSource}' +
'/stable/2018-01-01/examples/cloud-events-schema/';

type FileMetadata = {
name: string;
path: string;
sha: string;
size: number;
url: string;
html_url: string;
git_url: string;
download_url: string;
type: string;
_links: {
self: string;
git: string;
html: string;
};
};

export class EventGridTypeStep extends AzureWizardPromptStep<EventGridExecuteFunctionContext> {
public hideStepCount: boolean = false;

public async prompt(context: EventGridExecuteFunctionContext): Promise<void> {
if (!context.eventSource) {
throw new Error('Event source is required');
Comment thread
hossam-nasr marked this conversation as resolved.
Outdated
}

// Get sample files for event source
const samplesUrl = sampleFilesUrl.replace('{eventSource}', context.eventSource);
const sampleFiles: FileMetadata[] = await feedUtils.getJsonFeed(context, samplesUrl);
const fileNames: string[] = sampleFiles.map((fileMetadata) => fileMetadata.name);

// Prompt for event type
const eventTypePicks: IAzureQuickPickItem<string | undefined>[] = fileNames.map((name: string) => ({
data: name,
// give human-readable name for event type from file name
label: name
.replace(/\.json$/, '')
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '),
}));

context.selectedFileName =
(
await context.ui.showQuickPick(eventTypePicks, {
placeHolder: localize('selectEventType', 'Select the event type'),
stepName: 'eventType',
})
).data ?? 'blob_created.json';
Comment thread
hossam-nasr marked this conversation as resolved.
Outdated

context.selectedFileUrl = sampleFiles.find((fileMetadata) => fileMetadata.name === context.selectedFileName)?.download_url || sampleFiles[0].download_url;

}

public shouldPrompt(context: EventGridExecuteFunctionContext): boolean {
return !context.selectedFileName;
}
}
70 changes: 70 additions & 0 deletions src/commands/executeFunction/eventGrid/eventGridSources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

Comment thread
hossam-nasr marked this conversation as resolved.
/**
* Sources here were obtained as the names of sources found on
* the EventGrid samples in the azure-rest-api-specs repository:
* https://github.com/Azure/azure-rest-api-specs/tree/master/specification/eventgrid/data-plane
*/

export type EventGridSource =
| 'Microsoft.ApiManagement'
| 'Microsoft.AppConfiguration'
| 'Microsoft.AVS'
| 'Microsoft.Cache'
| 'Microsoft.Communication'
| 'Microsoft.ContainerRegistry'
| 'Microsoft.ContainerService'
| 'Microsoft.DataBox'
| 'Microsoft.Devices'
| 'Microsoft.EventHub'
| 'Microsoft.HealthcareApis'
| 'Microsoft.KeyVault'
| 'Microsoft.MachineLearningServices'
| 'Microsoft.Maps'
| 'Microsoft.Media'
| 'Microsoft.PolicyInsights'
| 'Microsoft.ResourceNotification'
| 'Microsoft.Resources'
| 'Microsoft.ServiceBus'
| 'Microsoft.SignalRService'
| 'Microsoft.Storage'
| 'Microsoft.Web'
| string;

export const supportedEventGridSources: EventGridSource[] = [
'Microsoft.ApiManagement',
'Microsoft.AppConfiguration',
'Microsoft.AVS',
'Microsoft.Cache',
'Microsoft.Communication',
'Microsoft.ContainerRegistry',
'Microsoft.ContainerService',
'Microsoft.DataBox',
'Microsoft.Devices',
'Microsoft.EventHub',
'Microsoft.HealthcareApis',
'Microsoft.KeyVault',
'Microsoft.MachineLearningServices',
'Microsoft.Maps',
'Microsoft.Media',
'Microsoft.PolicyInsights',
'Microsoft.ResourceNotification',
'Microsoft.Resources',
'Microsoft.ServiceBus',
'Microsoft.SignalRService',
'Microsoft.Storage',
'Microsoft.Web',
];

export const supportedEventGridSourceLabels: Map<EventGridSource, string> = new Map([
['Microsoft.Storage', 'Blob Storage'],
['Microsoft.EventHub', 'Event Hubs'],
['Microsoft.ServiceBus', 'Service Bus'],
['Microsoft.ContainerRegistry', 'Container Registry'],
['Microsoft.ApiManagement', 'API Management'],
['Microsoft.Resources', 'Resources'],
['Microsoft.HealthcareApis', 'Health Data Services'],
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizard, type AzureWizardExecuteStep, type AzureWizardPromptStep, type IActionContext } from '@microsoft/vscode-azext-utils';
import { localize } from '../../../localize';
import { type FunctionTreeItemBase } from '../../../tree/FunctionTreeItemBase';
import { type IFunction } from '../../../workspace/LocalFunction';
import { type EventGridExecuteFunctionContext } from './EventGridExecuteFunctionContext';
import { EventGridFileOpenStep } from './EventGridFileOpenStep';
import { EventGridSourceStep } from './EventGridSourceStep';
import { EventGridTypeStep } from './EventGridTypeStep';

export async function executeEventGridFunction(context: IActionContext, _node: FunctionTreeItemBase | IFunction): Promise<void> {
const title: string = localize('executeEGFunction', 'Execute Event Grid Function');

const promptSteps: AzureWizardPromptStep<EventGridExecuteFunctionContext>[] = [
new EventGridSourceStep(),
new EventGridTypeStep(),
];

const executeSteps: AzureWizardExecuteStep<EventGridExecuteFunctionContext>[] = [
new EventGridFileOpenStep(),
];

const wizardContext: EventGridExecuteFunctionContext = {
...context,
eventSource: undefined,
selectedFileName: undefined,
selectedFileUrl: undefined,
fileOpened: false,
};

const wizard: AzureWizard<EventGridExecuteFunctionContext> = new AzureWizard(wizardContext, {
title,
promptSteps,
executeSteps,
showLoadingPrompt: true,
});

await wizard.prompt();
await wizard.execute();
}
Loading