Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,7 @@
"title": "Azure Functions",
"properties": {
"azureFunctions.templateFilter": {
"deprecationMessage": "This setting is no longer used to filter templates, but is being left to explain what the filter enumerations mean.",
"scope": "resource",
"type": "string",
"default": "Verified",
Expand Down
82 changes: 24 additions & 58 deletions src/commands/createFunction/FunctionListStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep, type IActionContext, type IAzureQuickPickItem, type IAzureQuickPickOptions, type IWizardOptions } from '@microsoft/vscode-azext-utils';
import { AzureWizardPromptStep, type IAzureQuickPickItem, type IWizardOptions } from '@microsoft/vscode-azext-utils';
import * as escape from 'escape-string-regexp';
import { type FuncVersion } from '../../FuncVersion';
import { JavaBuildTool, ProjectLanguage, TemplateFilter, templateFilterSetting } from '../../constants';
import { JavaBuildTool, ProjectLanguage } from '../../constants';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { type FunctionTemplateBase, type IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { TemplateSchemaVersion } from '../../templates/TemplateProviderBase';
import { durableUtils } from '../../utils/durableUtils';
import { nonNullProp } from '../../utils/nonNull';
import { isNodeV4Plus, isPythonV2Plus, nodeV4Suffix } from '../../utils/programmingModelUtils';
import { getWorkspaceSetting, updateWorkspaceSetting } from '../../vsCodeConfig/settings';
import { getWorkspaceSetting } from '../../vsCodeConfig/settings';
import { FunctionSubWizard } from './FunctionSubWizard';
import { type IFunctionWizardContext } from './IFunctionWizardContext';
import { JobsListStep } from './JobsListStep';
Expand All @@ -39,7 +39,7 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
const language: ProjectLanguage = nonNullProp(context, 'language');
const version: FuncVersion = nonNullProp(context, 'version');
const templateProvider = ext.templateProvider.get(context);
const templates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, TemplateFilter.All, context.projectTemplateKey);
const templates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, context.projectTemplateKey);
const foundTemplate: FunctionTemplateBase | undefined = templates.find((t: FunctionTemplateBase) => {
if (this._options.templateId) {
const actualId: string = t.id.toLowerCase();
Expand Down Expand Up @@ -83,10 +83,6 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
}

public async prompt(context: IFunctionWizardContext): Promise<void> {
/* v2 schema doesn't have a template filter setting */
let templateFilter: TemplateFilter = context.templateSchemaVersion === TemplateSchemaVersion.v2 ? TemplateFilter.All :
getWorkspaceSetting<TemplateFilter>(templateFilterSetting, context.projectPath) || TemplateFilter.Verified;

const templateProvider = ext.templateProvider.get(context);
while (!context.functionTemplate) {
let placeHolder: string = this._isProjectWizard ?
Expand All @@ -97,17 +93,13 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
placeHolder += localize('templateSource', ' (Template source: "{0}")', templateProvider.templateSource)
}

const result: FunctionTemplateBase | TemplatePromptResult = (await context.ui.showQuickPick(this.getPicks(context, templateFilter), { placeHolder })).data;
const result: FunctionTemplateBase | TemplatePromptResult =
(await context.ui.showQuickPick(this.getPicks(context),
{ placeHolder, enableGrouping: true })).data;

if (result === 'skipForNow') {
context.telemetry.properties.templateId = 'skipForNow';
break;
} else if (result === 'changeFilter') {
templateFilter = await promptForTemplateFilter(context);
// can only update setting if it's open in a workspace
if (!this._isProjectWizard || context.openBehavior === 'AlreadyOpen') {
await updateWorkspaceSetting(templateFilterSetting, templateFilter, context.projectPath);
}
context.telemetry.properties.changedFilter = 'true';
} else if (result === 'openAPI') {
context.generateFromOpenAPI = true;
break;
Expand All @@ -118,26 +110,24 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
context.functionTemplate = result;
}
}

context.telemetry.properties.templateFilter = templateFilter;
}

public shouldPrompt(context: IFunctionWizardContext): boolean {
return !context.functionTemplate && context['buildTool'] !== JavaBuildTool.maven;
}

private async getPicks(context: IFunctionWizardContext, templateFilter: TemplateFilter): Promise<IAzureQuickPickItem<FunctionTemplateBase | TemplatePromptResult>[]> {
private async getPicks(context: IFunctionWizardContext): Promise<IAzureQuickPickItem<FunctionTemplateBase | TemplatePromptResult>[]> {
const language: ProjectLanguage = nonNullProp(context, 'language');
const languageModel = context.languageModel;
const version: FuncVersion = nonNullProp(context, 'version');
const templateProvider = ext.templateProvider.get(context);

const templates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, templateFilter, context.projectTemplateKey);
const templates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(context, context.projectPath, language, context.languageModel, version, context.projectTemplateKey);
context.telemetry.measurements.templateCount = templates.length;
const picks: IAzureQuickPickItem<FunctionTemplateBase | TemplatePromptResult>[] = templates
.filter((t) => !(doesTemplateRequireExistingStorageSetup(t.id, language) && !context.hasDurableStorage))
.sort((a, b) => sortTemplates(a, b, templateFilter))
.map(t => { return { label: t.name, data: t }; });
.sort((a, b) => sortTemplates(a, b))
.map(t => { return { label: t.name, data: t, group: t.templateFilter }; });

if (this._isProjectWizard) {
picks.unshift({
Expand Down Expand Up @@ -165,15 +155,6 @@ export class FunctionListStep extends AzureWizardPromptStep<IFunctionWizardConte
suppressPersistence: true
});
}
if (context.templateSchemaVersion !== TemplateSchemaVersion.v2) {
// don't offer template filter for v2 schema
picks.push({
label: localize('selectFilter', '$(gear) Change template filter'),
description: localize('currentFilter', 'Current: {0}', templateFilter),
data: 'changeFilter',
suppressPersistence: true
});
}

if (getWorkspaceSetting<boolean>('showReloadTemplates')) {
picks.push({
Expand All @@ -193,18 +174,7 @@ interface IFunctionListStepOptions {
functionSettings: { [key: string]: string | undefined } | undefined;
}

type TemplatePromptResult = 'changeFilter' | 'skipForNow' | 'openAPI' | 'reloadTemplates';

async function promptForTemplateFilter(context: IActionContext): Promise<TemplateFilter> {
const picks: IAzureQuickPickItem<TemplateFilter>[] = [
{ label: TemplateFilter.Verified, description: localize('verifiedDescription', '(Subset of "Core" that has been verified in VS Code)'), data: TemplateFilter.Verified },
{ label: TemplateFilter.Core, data: TemplateFilter.Core },
{ label: TemplateFilter.All, data: TemplateFilter.All }
];

const options: IAzureQuickPickOptions = { suppressPersistence: true, placeHolder: localize('selectFilter', 'Select a template filter') };
return (await context.ui.showQuickPick(picks, options)).data;
}
type TemplatePromptResult = 'skipForNow' | 'openAPI' | 'reloadTemplates';

// Todo: https://github.com/microsoft/vscode-azurefunctions/issues/3529
// Identify and filter out Durable Function templates requiring a pre-existing storage setup
Expand All @@ -226,21 +196,17 @@ function doesTemplateRequireExistingStorageSetup(templateId: string, language?:
* If templateFilter is verified, puts HttpTrigger/TimerTrigger at the top since they're the most popular
* Otherwise sort alphabetically
*/
function sortTemplates(a: FunctionTemplateBase, b: FunctionTemplateBase, templateFilter: TemplateFilter): number {
if (templateFilter === TemplateFilter.Verified) {
function getPriority(id: string): number {
if (/\bhttptrigger\b/i.test(id)) { // Plain http trigger
return 1;
} else if (/\bhttptrigger/i.test(id)) { // Http trigger with any extra pizazz
return 2;
} else if (/\btimertrigger\b/i.test(id)) {
return 3;
} else {
return 4;
}
function sortTemplates(a: FunctionTemplateBase, b: FunctionTemplateBase): number {
function getPriority(id: string): number {
if (/\bhttptrigger\b/i.test(id)) { // Plain http trigger
return 1;
} else if (/\bhttptrigger/i.test(id)) { // Http trigger with any extra pizazz
return 2;
} else if (/\btimertrigger\b/i.test(id)) {
return 3;
} else {
return a.name.localeCompare(b.name) === -1 ? 4 : 5;
}
return getPriority(a.id) - getPriority(b.id);
}

return a.name.localeCompare(b.name);
return getPriority(a.id) - getPriority(b.id);
}
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export const projectLanguageSetting: string = 'projectLanguage';
export const projectLanguageModelSetting: string = 'projectLanguageModel';
export const funcVersionSetting: string = 'projectRuntime'; // Using this name for the sake of backwards compatability even though it's not the most accurate
export const projectSubpathSetting: string = 'projectSubpath';
export const templateFilterSetting: string = 'templateFilter';
export const deploySubpathSetting: string = 'deploySubpath';
export const templateVersionSetting: string = 'templateVersion';
export const preDeployTaskSetting: string = 'preDeployTask';
Expand Down
24 changes: 13 additions & 11 deletions src/templates/CentralTemplateProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,20 @@ export class CentralTemplateProvider implements Disposable {
}

/* Ignored by the v2 schema */
public async getFunctionTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, templateFilter: TemplateFilter, projectTemplateKey: string | undefined): Promise<FunctionTemplateBase[]> {
public async getFunctionTemplates(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion, projectTemplateKey: string | undefined): Promise<FunctionTemplateBase[]> {
const templates: ITemplates = await this.getTemplates(context, projectPath, language, languageModel, version, projectTemplateKey);
switch (templateFilter) {
case TemplateFilter.All:
return templates.functionTemplates;
case TemplateFilter.Core:
return templates.functionTemplates.filter((t: IFunctionTemplate) => t.categories.find((c: TemplateCategory) => c === TemplateCategory.Core) !== undefined);
case TemplateFilter.Verified:
default:
const verifiedTemplateIds = getScriptVerifiedTemplateIds(version).concat(getDotnetVerifiedTemplateIds(version)).concat(getJavaVerifiedTemplateIds().concat(getBallerinaVerifiedTemplateIds()));
return templates.functionTemplates.filter((t: IFunctionTemplate) => verifiedTemplateIds.find(vt => typeof vt === 'string' ? vt === t.id : vt.test(t.id)));
for (const template of templates.functionTemplates) {
// by default, all templates will be categorized as 'All'
template.templateFilter = TemplateFilter.All;
const verifiedTemplateIds = getScriptVerifiedTemplateIds(version).concat(getDotnetVerifiedTemplateIds(version)).concat(getJavaVerifiedTemplateIds().concat(getBallerinaVerifiedTemplateIds()));
if (verifiedTemplateIds.find(vt => typeof vt === 'string' ? vt === template.id : vt.test(template.id))) {
template.templateFilter = TemplateFilter.Verified;
} else if ((template as IFunctionTemplate).categories.find((c: TemplateCategory) => c === TemplateCategory.Core) !== undefined) {
template.templateFilter = TemplateFilter.Core;
}
}

return templates.functionTemplates;
}

public async clearTemplateCache(context: IActionContext, projectPath: string | undefined, language: ProjectLanguage, languageModel: number | undefined, version: FuncVersion): Promise<void> {
Expand All @@ -117,7 +119,7 @@ export class CentralTemplateProvider implements Disposable {

public async tryGetSampleData(context: IActionContext, version: FuncVersion, triggerBindingType: string): Promise<string | undefined> {
try {
const templates: IScriptFunctionTemplate[] = <IScriptFunctionTemplate[]>await this.getFunctionTemplates(context, undefined, ProjectLanguage.JavaScript, undefined, version, TemplateFilter.All, undefined);
const templates: IScriptFunctionTemplate[] = <IScriptFunctionTemplate[]>await this.getFunctionTemplates(context, undefined, ProjectLanguage.JavaScript, undefined, version, undefined);
const template: IScriptFunctionTemplate | undefined = templates.find(t => t.functionJson.triggerBinding?.type?.toLowerCase() === triggerBindingType.toLowerCase());
return template?.templateFiles['sample.dat'];
} catch {
Expand Down
5 changes: 3 additions & 2 deletions src/templates/IFunctionTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type ProjectLanguage } from '../constants';
import { type ProjectLanguage, type TemplateFilter } from '../constants';
import { type IBindingSetting } from './IBindingTemplate';
import { type TemplateSchemaVersion } from './TemplateProviderBase';
import { type ParsedJob, type RawTemplateV2 } from './script/parseScriptTemplatesV2';
Expand Down Expand Up @@ -44,5 +44,6 @@ export interface FunctionTemplateBase {
language: ProjectLanguage;
isHttpTrigger: boolean;
isTimerTrigger: boolean;
templateSchemaVersion: TemplateSchemaVersion
templateSchemaVersion: TemplateSchemaVersion;
templateFilter?: TemplateFilter; // defaults to All
}
5 changes: 3 additions & 2 deletions src/templates/dotnet/parseDotnetTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ function parseDotnetTemplate(rawTemplate: IRawTemplate): IFunctionTemplate {
userPromptedSettings: userPromptedSettings,
categories: [TemplateCategory.Core], // Dotnet templates do not have category information, so display all templates as if they are in the 'core' category
isDynamicConcurrent: (rawTemplate.Identity.includes('ServiceBusQueueTrigger') || rawTemplate.Identity.includes('BlobTrigger') || rawTemplate.Identity.includes('QueueTrigger')) ? true : false,
templateSchemaVersion: TemplateSchemaVersion.v1
templateSchemaVersion: TemplateSchemaVersion.v1,
templateFilter: TemplateFilter.All
};
}

Expand Down Expand Up @@ -122,7 +123,7 @@ async function copyCSharpSettingsFromJS(csharpTemplates: IFunctionTemplate[], ve
jsContext.telemetry.properties.isActivationEvent = 'true';

const templateProvider = ext.templateProvider.get(jsContext);
const jsTemplates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(jsContext, undefined, ProjectLanguage.JavaScript, undefined, version, TemplateFilter.All, undefined);
const jsTemplates: FunctionTemplateBase[] = await templateProvider.getFunctionTemplates(jsContext, undefined, ProjectLanguage.JavaScript, undefined, version, undefined);
for (const csharpTemplate of csharpTemplates) {
assertTemplateIsV1(csharpTemplate);
csharpTemplate.templateSchemaVersion = TemplateSchemaVersion.v1;
Expand Down
4 changes: 2 additions & 2 deletions src/vsCodeConfig/verifyVSCodeConfigOnActivate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as path from 'path';
import type * as vscode from 'vscode';
import { tryGetFunctionProjectRoot } from '../commands/createNewProject/verifyIsProject';
import { initProjectForVSCode } from '../commands/initProjectForVSCode/initProjectForVSCode';
import { funcVersionSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting, TemplateFilter } from '../constants';
import { funcVersionSetting, ProjectLanguage, projectLanguageModelSetting, projectLanguageSetting } from '../constants';
import { ext } from '../extensionVariables';
import { tryParseFuncVersion, type FuncVersion } from '../FuncVersion';
import { localize } from '../localize';
Expand Down Expand Up @@ -38,7 +38,7 @@ export async function verifyVSCodeConfigOnActivate(context: IActionContext, fold
templatesContext.telemetry.properties.isActivationEvent = 'true';
templatesContext.errorHandling.suppressDisplay = true;
const templateProvider = ext.templateProvider.get(templatesContext);
await templateProvider.getFunctionTemplates(templatesContext, projectPath, language, languageModel, version, TemplateFilter.Verified, undefined);
await templateProvider.getFunctionTemplates(templatesContext, projectPath, language, languageModel, version, undefined);
});

let isDotnet: boolean = false;
Expand Down
Loading