From 83e8518503a142ebcd14a6c9c03aa1dadb336e02 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 16 Jul 2020 14:58:20 -0400 Subject: [PATCH 01/15] Extend cmd customization for context types --- .eslintrc.js | 2 +- package.json | 103 +++++++++++++++++++++++++- package.nls.json | 1 + src/commands/selectCommandTemplate.ts | 57 ++++++++++---- src/docker/ContextManager.ts | 32 ++++++-- src/docker/Contexts.ts | 16 +++- 6 files changed, 189 insertions(+), 22 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5f98bec5d9..e85dd6e02b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -147,7 +147,7 @@ module.exports = { "max-lines": "off", "new-parens": "error", "newline-per-chained-call": "off", - "no-bitwise": "error", + "no-bitwise": "off", "no-caller": "error", "no-cond-assign": "error", "no-console": [ diff --git a/package.json b/package.json index 2c74d8a4e9..7a5105bd2c 100644 --- a/package.json +++ b/package.json @@ -1418,6 +1418,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.build.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1450,6 +1461,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.run.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1482,6 +1504,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.runInteractive.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1514,6 +1547,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.attach.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1546,6 +1590,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.logs.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1578,6 +1633,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.composeUp.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1590,7 +1656,18 @@ "type": "string" } ], - "default": "docker-compose ${configurationFile} up ${detached} ${build}", + "default": [ + { + "label": "Compose Up", + "template": "docker-compose ${configurationFile} up ${detached} ${build}", + "contextType": "downlevel" + }, + { + "label": "Compose Up", + "template": "docker compose ${configurationFile} up ${detached}", + "contextType": "uplevel" + } + ], "description": "%vscode-docker.config.template.composeUp.description%" }, "docker.commands.composeDown": { @@ -1610,6 +1687,17 @@ "match": { "type": "string", "description": "%vscode-docker.config.template.composeDown.match%" + }, + "contextType": { + "type": "string", + "description": "%vscode-docker.config.template.contextType.description%", + "enum": [ + "all", + "downlevel", + "uplevel", + "aci" + ], + "default": "all" } }, "required": [ @@ -1622,7 +1710,18 @@ "type": "string" } ], - "default": "docker-compose ${configurationFile} down", + "default": [ + { + "label": "Compose Down", + "template": "docker-compose ${configurationFile} down", + "contextType": "downlevel" + }, + { + "label": "Compose Down", + "template": "docker compose ${configurationFile} down", + "contextType": "uplevel" + } + ], "description": "%vscode-docker.config.template.composeDown.description%" }, "docker.containers.groupBy": { diff --git a/package.nls.json b/package.nls.json index 4bce7d731e..e2011d0f04 100644 --- a/package.nls.json +++ b/package.nls.json @@ -125,6 +125,7 @@ "vscode-docker.config.template.composeDown.label": "The label displayed to the user.", "vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.", "vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.", + "vscode-docker.config.template.contextType.description": "The context type in which the command applies. 'uplevel' includes all new contexts (currently, 'aci').", "vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)", "vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None", "vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag", diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 29f8d935d1..21cd375a67 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui'; +import { DockerContextTypes } from '../docker/Contexts'; import { ext } from '../extensionVariables'; import { localize } from '../localize'; import { resolveVariables } from '../utils/resolveVariables'; @@ -15,19 +16,27 @@ type CommandTemplate = { template: string, label: string, match?: string, + contextType?: string, + parsedContextType: DockerContextTypes, }; // NOTE: the default templates are duplicated in package.json, since VSCode offers no way of looking up extension-level default settings // So, when modifying them here, be sure to modify them there as well! -const defaults: { [key in TemplateCommand]: CommandTemplate } = { +const defaults: { [key in TemplateCommand]: CommandTemplate[] } = { /* eslint-disable no-template-curly-in-string */ - 'build': { label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"' }, - 'run': { label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}' }, - 'runInteractive': { label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}' }, - 'attach': { label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}' }, - 'logs': { label: 'Docker Logs', template: 'docker logs -f ${containerId}' }, - 'composeUp': { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}' }, - 'composeDown': { label: 'Compose Down', template: 'docker-compose ${configurationFile} down' }, + 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"', parsedContextType: DockerContextTypes.all }], + 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}', parsedContextType: DockerContextTypes.all }], + 'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}', parsedContextType: DockerContextTypes.all }], + 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}', parsedContextType: DockerContextTypes.all }], + 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}', parsedContextType: DockerContextTypes.all }], + 'composeUp': [ + { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', parsedContextType: DockerContextTypes.downlevel }, + { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', parsedContextType: DockerContextTypes.uplevel }, + ], + 'composeDown': [ + { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', parsedContextType: DockerContextTypes.downlevel }, + { label: 'Compose Down', template: 'docker compose ${configurationFile} down', parsedContextType: DockerContextTypes.uplevel }, + ], /* eslint-enable no-template-curly-in-string */ }; @@ -89,6 +98,10 @@ export async function selectComposeCommand(context: IActionContext, folder: vsco } async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext?: string[], folder?: vscode.WorkspaceFolder, additionalVariables?: { [key: string]: string }): Promise { + // Get the current context type + const currentContext = await ext.dockerContextManager.getCurrentContext(); + const currentContextType = currentContext.ContextType; + // Get the templates from settings const config = vscode.workspace.getConfiguration('docker'); const templateSetting: CommandTemplate[] | string = config.get(`commands.${command}`); @@ -104,8 +117,21 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC templates = templateSetting; } - // Look for settings-defined template(s) with explicit match, that matches the context + // Set the parsedContextType on each template + templates.forEach(template => { + try { + template.parsedContextType = template.parsedContextType ?? template.contextType ? DockerContextTypes[template.contextType] as DockerContextTypes : DockerContextTypes.all; + } catch { + template.parsedContextType = DockerContextTypes.all; + } + }); + + // Look for settings-defined template(s) with explicit match, that matches the match context and the current Docker context type const matchedTemplates = templates.filter(template => { + if (!(template.parsedContextType & currentContextType)) { + return false; + } + if (template.match) { try { const matcher = new RegExp(template.match, 'i'); @@ -120,8 +146,11 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC return false; }); - // Look for settings-defined template(s) with no explicit match - const universalTemplates = templates.filter(template => !template.match); + // Look for settings-defined template(s) with no explicit match and the current Docker context type + const universalTemplates = templates.filter(template => !template.match && (template.parsedContextType & currentContextType)); + + // Get the default templates from code above that match the current context (hopefully just one) + const defaultCommandsForContext = defaults[command].filter(template => template.parsedContextType & currentContextType); // Select from explicit match templates, if none then from settings-defined universal templates, if none then hardcoded default let selectedTemplate: CommandTemplate; @@ -130,11 +159,13 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC } else if (universalTemplates.length > 0) { selectedTemplate = await quickPickTemplate(context, universalTemplates); } else { - selectedTemplate = defaults[command]; + selectedTemplate = await quickPickTemplate(context, defaultCommandsForContext); } - context.telemetry.properties.isDefaultCommand = selectedTemplate.template === defaults[command].template ? 'true' : 'false'; + context.telemetry.properties.isDefaultCommand = defaultCommandsForContext.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; + context.telemetry.properties.commandContextType = selectedTemplate.parsedContextType.toString(); // TODO: validate this even works + context.telemetry.properties.currentContextType = currentContextType.toString(); return resolveVariables(selectedTemplate.template, folder, additionalVariables); } diff --git a/src/docker/ContextManager.ts b/src/docker/ContextManager.ts index 47f64a08ad..0faa71864f 100644 --- a/src/docker/ContextManager.ts +++ b/src/docker/ContextManager.ts @@ -40,6 +40,19 @@ const defaultContext: Partial = { Description: 'Current DOCKER_HOST based configuration', }; +export enum DockerContextTypes { + // All downlevel context types + downlevel = 1 << 1, + + // All uplevel context types + aci = 1 << 15, + + uplevel = aci, + + // All context types (combines downlevel and uplevel) + all = downlevel | uplevel, +} + type VSCodeContext = 'vscode-docker:aciContext' | 'vscode-docker:newSdkContext' | 'vscode-docker:newCliPresent'; export interface ContextManager { @@ -111,7 +124,7 @@ export class DockerContextManager implements ContextManager, Disposable { void ext.dockerClient?.dispose(); // Create a new client - if (currentContext.Type === 'aci') { + if (currentContext.ContextType & DockerContextTypes.uplevel) { // Currently vscode-docker:aciContext vscode-docker:newSdkContext mean the same thing // But that probably won't be true in the future, so define both as separate concepts now await this.setVsCodeContext('vscode-docker:aciContext', true); @@ -204,10 +217,19 @@ export class DockerContextManager implements ContextManager, Disposable { for (const line of lines) { const context = JSON.parse(line) as DockerContext; + + let contextType: DockerContextTypes; + if ((context.Type ?? 'aci' === 'aci') && !context.DockerEndpoint) { + // TODO: this basically assumes no Type and no DockerEndpoint => aci + contextType = DockerContextTypes.aci; + } else { + contextType = DockerContextTypes.downlevel; + } + result.push({ ...context, Id: context.Name, - Type: context.Type || context.DockerEndpoint ? 'moby' : 'aci', // TODO: this basically assumes no Type and no DockerEndpoint => aci + ContextType: contextType, }); } @@ -242,7 +264,7 @@ export class DockerContextManager implements ContextManager, Disposable { ...defaultContext, Current: true, DockerEndpoint: os.platform() === 'win32' ? WindowsLocalPipe : UnixLocalPipe, - Type: 'moby', + ContextType: DockerContextTypes.downlevel, } as DockerContext]; } @@ -253,8 +275,8 @@ export class DockerContextManager implements ContextManager, Disposable { let result: boolean = false; const contexts = await this.contextsCache.getValue(); - if (contexts.some(c => c.Type === 'aci')) { - // If there are any ACI contexts we automatically know it's the new CLI + if (contexts.some(c => c.ContextType & DockerContextTypes.uplevel)) { + // If there are any uplevel contexts we automatically know it's the new CLI result = true; } else { // Otherwise we look at the output of `docker serve --help` diff --git a/src/docker/Contexts.ts b/src/docker/Contexts.ts index 264d6eb3b6..fe488ad599 100644 --- a/src/docker/Contexts.ts +++ b/src/docker/Contexts.ts @@ -9,7 +9,8 @@ export interface DockerContext extends DockerObject { readonly Description?: string; readonly DockerEndpoint: string; readonly Current: boolean; - readonly Type: 'aci' | 'moby'; + readonly Type?: string; + readonly ContextType: DockerContextTypes; readonly Id: string; // Will be equal to Name for contexts @@ -19,3 +20,16 @@ export interface DockerContext extends DockerObject { export interface DockerContextInspection { readonly [key: string]: unknown; } + +export enum DockerContextTypes { + // All downlevel context types + downlevel = 1 << 1, + + // All uplevel context types + aci = 1 << 15, + + uplevel = aci, + + // All context types (combines downlevel and uplevel) + all = downlevel | uplevel +} From 74304060ddb8f12be67e26f47b307791855d5792 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 16 Jul 2020 15:07:31 -0400 Subject: [PATCH 02/15] Unit tests --- extension.bundle.ts | 1 + src/commands/selectCommandTemplate.ts | 2 +- test/docker/dockerContextTypes.test.ts | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 test/docker/dockerContextTypes.test.ts diff --git a/extension.bundle.ts b/extension.bundle.ts index b942dcfa94..d4eb99da20 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -52,5 +52,6 @@ export { DockerContainer } from './src/docker/Containers'; export { DockerImage } from './src/docker/Images'; export { DockerNetwork } from './src/docker/Networks'; export { DockerVolume } from './src/docker/Volumes'; +export { DockerContextTypes } from './src/docker/Contexts'; export * from 'vscode-azureextensionui'; diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 21cd375a67..7a91990cd0 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -164,7 +164,7 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC context.telemetry.properties.isDefaultCommand = defaultCommandsForContext.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; - context.telemetry.properties.commandContextType = selectedTemplate.parsedContextType.toString(); // TODO: validate this even works + context.telemetry.properties.commandContextType = selectedTemplate.parsedContextType.toString(); // TODO: validate this even works // TODO: it does not context.telemetry.properties.currentContextType = currentContextType.toString(); return resolveVariables(selectedTemplate.template, folder, additionalVariables); diff --git a/test/docker/dockerContextTypes.test.ts b/test/docker/dockerContextTypes.test.ts new file mode 100644 index 0000000000..c799f3042d --- /dev/null +++ b/test/docker/dockerContextTypes.test.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { DockerContextTypes } from '../../extension.bundle'; + +const testData: { left: DockerContextTypes, right: DockerContextTypes, expect: boolean }[] = [ + { left: DockerContextTypes.downlevel, right: DockerContextTypes.aci, expect: false }, +] + +suite('(unit) DockerContextTypes tests', () => { + testData.forEach(t => { + test(`${t.left.toString()} & ${t.right.toString()} == ${t.expect.toString()}`, () => { + assert.equal(Boolean(t.left & t.right), t.expect, ''); + }); + }); +}); From 4d128b85cc1c1294f3424e7e8cd6d2350ee22d91 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:14:53 -0400 Subject: [PATCH 03/15] Refactoring to be less confusing --- extension.bundle.ts | 1 - src/commands/selectCommandTemplate.ts | 64 +++++++++++++++----------- src/docker/ContextManager.ts | 32 ++----------- src/docker/Contexts.ts | 23 ++++----- test/docker/dockerContextTypes.test.ts | 19 -------- 5 files changed, 51 insertions(+), 88 deletions(-) delete mode 100644 test/docker/dockerContextTypes.test.ts diff --git a/extension.bundle.ts b/extension.bundle.ts index 59e7ba6b84..9941ab7282 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -52,6 +52,5 @@ export { DockerContainer } from './src/docker/Containers'; export { DockerImage } from './src/docker/Images'; export { DockerNetwork } from './src/docker/Networks'; export { DockerVolume } from './src/docker/Volumes'; -export { DockerContextTypes } from './src/docker/Contexts'; export * from 'vscode-azureextensionui'; diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 7a91990cd0..4342218fac 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -5,37 +5,38 @@ import * as vscode from 'vscode'; import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui'; -import { DockerContextTypes } from '../docker/Contexts'; +import { ContextType, isUplevelContextType } from '../docker/Contexts'; import { ext } from '../extensionVariables'; import { localize } from '../localize'; import { resolveVariables } from '../utils/resolveVariables'; -export type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown'; +type TemplateContextType = 'all' | 'downlevel' | 'uplevel' | 'aci'; + +type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown'; type CommandTemplate = { template: string, label: string, match?: string, - contextType?: string, - parsedContextType: DockerContextTypes, + contextType?: TemplateContextType, }; // NOTE: the default templates are duplicated in package.json, since VSCode offers no way of looking up extension-level default settings // So, when modifying them here, be sure to modify them there as well! const defaults: { [key in TemplateCommand]: CommandTemplate[] } = { /* eslint-disable no-template-curly-in-string */ - 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"', parsedContextType: DockerContextTypes.all }], - 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}', parsedContextType: DockerContextTypes.all }], - 'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}', parsedContextType: DockerContextTypes.all }], - 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}', parsedContextType: DockerContextTypes.all }], - 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}', parsedContextType: DockerContextTypes.all }], + 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"', contextType: 'all' }], + 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}', contextType: 'all' }], + 'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}', contextType: 'all' }], + 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}', contextType: 'all' }], + 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}', contextType: 'all' }], 'composeUp': [ - { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', parsedContextType: DockerContextTypes.downlevel }, - { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', parsedContextType: DockerContextTypes.uplevel }, + { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextType: 'downlevel' }, + { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', contextType: 'uplevel' }, ], 'composeDown': [ - { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', parsedContextType: DockerContextTypes.downlevel }, - { label: 'Compose Down', template: 'docker compose ${configurationFile} down', parsedContextType: DockerContextTypes.uplevel }, + { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextType: 'downlevel' }, + { label: 'Compose Down', template: 'docker compose ${configurationFile} down', contextType: 'uplevel' }, ], /* eslint-enable no-template-curly-in-string */ }; @@ -100,7 +101,7 @@ export async function selectComposeCommand(context: IActionContext, folder: vsco async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext?: string[], folder?: vscode.WorkspaceFolder, additionalVariables?: { [key: string]: string }): Promise { // Get the current context type const currentContext = await ext.dockerContextManager.getCurrentContext(); - const currentContextType = currentContext.ContextType; + const currentContextType = currentContext.Type; // Get the templates from settings const config = vscode.workspace.getConfiguration('docker'); @@ -117,18 +118,9 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC templates = templateSetting; } - // Set the parsedContextType on each template - templates.forEach(template => { - try { - template.parsedContextType = template.parsedContextType ?? template.contextType ? DockerContextTypes[template.contextType] as DockerContextTypes : DockerContextTypes.all; - } catch { - template.parsedContextType = DockerContextTypes.all; - } - }); - // Look for settings-defined template(s) with explicit match, that matches the match context and the current Docker context type const matchedTemplates = templates.filter(template => { - if (!(template.parsedContextType & currentContextType)) { + if (!isMatchingContextType(currentContextType, template.contextType)) { return false; } @@ -147,10 +139,10 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC }); // Look for settings-defined template(s) with no explicit match and the current Docker context type - const universalTemplates = templates.filter(template => !template.match && (template.parsedContextType & currentContextType)); + const universalTemplates = templates.filter(template => !template.match && isMatchingContextType(currentContextType, template.contextType)); // Get the default templates from code above that match the current context (hopefully just one) - const defaultCommandsForContext = defaults[command].filter(template => template.parsedContextType & currentContextType); + const defaultCommandsForContext = defaults[command].filter(template => isMatchingContextType(currentContextType, template.contextType)); // Select from explicit match templates, if none then from settings-defined universal templates, if none then hardcoded default let selectedTemplate: CommandTemplate; @@ -164,8 +156,8 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC context.telemetry.properties.isDefaultCommand = defaultCommandsForContext.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; - context.telemetry.properties.commandContextType = selectedTemplate.parsedContextType.toString(); // TODO: validate this even works // TODO: it does not - context.telemetry.properties.currentContextType = currentContextType.toString(); + context.telemetry.properties.commandContextType = selectedTemplate.contextType ?? 'all'; + context.telemetry.properties.currentContextType = currentContextType; return resolveVariables(selectedTemplate.template, folder, additionalVariables); } @@ -190,3 +182,19 @@ async function quickPickTemplate(context: IActionContext, templates: CommandTemp return selection.data; } + +function isMatchingContextType(currentContextType: ContextType, templateContextType: TemplateContextType | undefined): boolean { + templateContextType = templateContextType ?? 'all'; + + switch (templateContextType) { + case 'uplevel': + return isUplevelContextType(currentContextType); + case 'downlevel': + return !isUplevelContextType(currentContextType); + case 'aci': + return currentContextType === 'aci'; + case 'all': + default: + return true; + } +} diff --git a/src/docker/ContextManager.ts b/src/docker/ContextManager.ts index e012677d1d..b0d8e02c66 100644 --- a/src/docker/ContextManager.ts +++ b/src/docker/ContextManager.ts @@ -16,7 +16,7 @@ import { LineSplitter } from '../debugging/coreclr/lineSplitter'; import { ext } from '../extensionVariables'; import { AsyncLazy } from '../utils/lazy'; import { execAsync, spawnAsync } from '../utils/spawnAsync'; -import { DockerContext, DockerContextInspection } from './Contexts'; +import { DockerContext, DockerContextInspection, isUplevelContextType } from './Contexts'; import { DockerodeApiClient } from './DockerodeApiClient/DockerodeApiClient'; import { DockerServeClient } from './DockerServeClient/DockerServeClient'; @@ -40,19 +40,6 @@ const defaultContext: Partial = { Description: 'Current DOCKER_HOST based configuration', }; -export enum DockerContextTypes { - // All downlevel context types - downlevel = 1 << 1, - - // All uplevel context types - aci = 1 << 15, - - uplevel = aci, - - // All context types (combines downlevel and uplevel) - all = downlevel | uplevel, -} - // These contexts are used by external consumers (e.g. the "Remote - Containers" extension), and should NOT be changed type VSCodeContext = 'vscode-docker:aciContext' | 'vscode-docker:newSdkContext' | 'vscode-docker:newCliPresent'; @@ -125,7 +112,7 @@ export class DockerContextManager implements ContextManager, Disposable { void ext.dockerClient?.dispose(); // Create a new client - if (currentContext.ContextType & DockerContextTypes.uplevel) { + if (isUplevelContextType(currentContext.Type)) { // Currently vscode-docker:aciContext vscode-docker:newSdkContext mean the same thing // But that probably won't be true in the future, so define both as separate concepts now await this.setVsCodeContext('vscode-docker:aciContext', true); @@ -218,19 +205,10 @@ export class DockerContextManager implements ContextManager, Disposable { for (const line of lines) { const context = JSON.parse(line) as DockerContext; - - let contextType: DockerContextTypes; - if ((context.Type ?? 'aci' === 'aci') && !context.DockerEndpoint) { - // TODO: this basically assumes no Type and no DockerEndpoint => aci - contextType = DockerContextTypes.aci; - } else { - contextType = DockerContextTypes.downlevel; - } - result.push({ ...context, Id: context.Name, - ContextType: contextType, + Type: context.Type || context.DockerEndpoint ? 'moby' : 'aci', // TODO: this basically assumes no Type and no DockerEndpoint => aci }); } @@ -265,7 +243,7 @@ export class DockerContextManager implements ContextManager, Disposable { ...defaultContext, Current: true, DockerEndpoint: os.platform() === 'win32' ? WindowsLocalPipe : UnixLocalPipe, - ContextType: DockerContextTypes.downlevel, + Type: 'moby', } as DockerContext]; } @@ -276,7 +254,7 @@ export class DockerContextManager implements ContextManager, Disposable { let result: boolean = false; const contexts = await this.contextsCache.getValue(); - if (contexts.some(c => c.ContextType & DockerContextTypes.uplevel)) { + if (contexts.some(c => isUplevelContextType(c.Type))) { // If there are any uplevel contexts we automatically know it's the new CLI result = true; } else { diff --git a/src/docker/Contexts.ts b/src/docker/Contexts.ts index fe488ad599..96e861abef 100644 --- a/src/docker/Contexts.ts +++ b/src/docker/Contexts.ts @@ -5,12 +5,13 @@ import { DockerObject } from './Common'; +export type ContextType = 'aci' | 'moby'; + export interface DockerContext extends DockerObject { readonly Description?: string; readonly DockerEndpoint: string; readonly Current: boolean; - readonly Type?: string; - readonly ContextType: DockerContextTypes; + readonly Type: ContextType; readonly Id: string; // Will be equal to Name for contexts @@ -21,15 +22,11 @@ export interface DockerContextInspection { readonly [key: string]: unknown; } -export enum DockerContextTypes { - // All downlevel context types - downlevel = 1 << 1, - - // All uplevel context types - aci = 1 << 15, - - uplevel = aci, - - // All context types (combines downlevel and uplevel) - all = downlevel | uplevel +export function isUplevelContextType(contextType: ContextType): boolean { + switch (contextType) { + case 'aci': + return true; + default: + return false; + } } diff --git a/test/docker/dockerContextTypes.test.ts b/test/docker/dockerContextTypes.test.ts deleted file mode 100644 index c799f3042d..0000000000 --- a/test/docker/dockerContextTypes.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { DockerContextTypes } from '../../extension.bundle'; - -const testData: { left: DockerContextTypes, right: DockerContextTypes, expect: boolean }[] = [ - { left: DockerContextTypes.downlevel, right: DockerContextTypes.aci, expect: false }, -] - -suite('(unit) DockerContextTypes tests', () => { - testData.forEach(t => { - test(`${t.left.toString()} & ${t.right.toString()} == ${t.expect.toString()}`, () => { - assert.equal(Boolean(t.left & t.right), t.expect, ''); - }); - }); -}); From 79610ac8582fa82c6db0b55550c38fd0b22b7f35 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:16:25 -0400 Subject: [PATCH 04/15] Renaming --- src/commands/selectCommandTemplate.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 4342218fac..74cf30a949 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -120,7 +120,7 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC // Look for settings-defined template(s) with explicit match, that matches the match context and the current Docker context type const matchedTemplates = templates.filter(template => { - if (!isMatchingContextType(currentContextType, template.contextType)) { + if (!currentContextTypeMatchesTemplate(currentContextType, template.contextType)) { return false; } @@ -139,10 +139,10 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC }); // Look for settings-defined template(s) with no explicit match and the current Docker context type - const universalTemplates = templates.filter(template => !template.match && isMatchingContextType(currentContextType, template.contextType)); + const universalTemplates = templates.filter(template => !template.match && currentContextTypeMatchesTemplate(currentContextType, template.contextType)); // Get the default templates from code above that match the current context (hopefully just one) - const defaultCommandsForContext = defaults[command].filter(template => isMatchingContextType(currentContextType, template.contextType)); + const defaultCommandsForContext = defaults[command].filter(template => currentContextTypeMatchesTemplate(currentContextType, template.contextType)); // Select from explicit match templates, if none then from settings-defined universal templates, if none then hardcoded default let selectedTemplate: CommandTemplate; @@ -183,7 +183,7 @@ async function quickPickTemplate(context: IActionContext, templates: CommandTemp return selection.data; } -function isMatchingContextType(currentContextType: ContextType, templateContextType: TemplateContextType | undefined): boolean { +function currentContextTypeMatchesTemplate(currentContextType: ContextType, templateContextType: TemplateContextType | undefined): boolean { templateContextType = templateContextType ?? 'all'; switch (templateContextType) { From 9321bc42e70a081cf20e4b6e7034f0a683215218 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:21:33 -0400 Subject: [PATCH 05/15] More future-proof code --- .eslintrc.js | 2 +- src/commands/selectCommandTemplate.ts | 8 ++++---- src/docker/Contexts.ts | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e85dd6e02b..5f98bec5d9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -147,7 +147,7 @@ module.exports = { "max-lines": "off", "new-parens": "error", "newline-per-chained-call": "off", - "no-bitwise": "off", + "no-bitwise": "error", "no-caller": "error", "no-cond-assign": "error", "no-console": [ diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 74cf30a949..a6354e3f95 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -184,17 +184,17 @@ async function quickPickTemplate(context: IActionContext, templates: CommandTemp } function currentContextTypeMatchesTemplate(currentContextType: ContextType, templateContextType: TemplateContextType | undefined): boolean { - templateContextType = templateContextType ?? 'all'; + templateContextType = templateContextType || 'all'; switch (templateContextType) { case 'uplevel': return isUplevelContextType(currentContextType); case 'downlevel': return !isUplevelContextType(currentContextType); - case 'aci': - return currentContextType === 'aci'; case 'all': - default: return true; + case 'aci': + default: + return currentContextType === templateContextType; } } diff --git a/src/docker/Contexts.ts b/src/docker/Contexts.ts index 96e861abef..4af99b79dc 100644 --- a/src/docker/Contexts.ts +++ b/src/docker/Contexts.ts @@ -24,9 +24,10 @@ export interface DockerContextInspection { export function isUplevelContextType(contextType: ContextType): boolean { switch (contextType) { - case 'aci': - return true; - default: + case 'moby': return false; + case 'aci': // ACI is new + default: // Anything else is likely a new context type as well + return true; } } From cdd89939dba565c1b47f7027ebd4b7bc9356d87d Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:22:37 -0400 Subject: [PATCH 06/15] Comment --- src/commands/selectCommandTemplate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index a6354e3f95..c3825d7b48 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -194,7 +194,7 @@ function currentContextTypeMatchesTemplate(currentContextType: ContextType, temp case 'all': return true; case 'aci': - default: + default: // Using this code as the default means unknown values can still be made to work return currentContextType === templateContextType; } } From 29911409e8f864a5a5c46c61e35006103dca33c7 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 20 Jul 2020 15:23:55 -0400 Subject: [PATCH 07/15] Change downlevel / uplevel --- package.json | 36 +++++++++++++-------------- package.nls.json | 2 +- src/commands/selectCommandTemplate.ts | 20 +++++++-------- src/docker/ContextManager.ts | 8 +++--- src/docker/Contexts.ts | 2 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index bc3407dfd5..e10b7e808b 100644 --- a/package.json +++ b/package.json @@ -1424,8 +1424,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1467,8 +1467,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1510,8 +1510,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1553,8 +1553,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1596,8 +1596,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1639,8 +1639,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1660,12 +1660,12 @@ { "label": "Compose Up", "template": "docker-compose ${configurationFile} up ${detached} ${build}", - "contextType": "downlevel" + "contextType": "legacy" }, { "label": "Compose Up", "template": "docker compose ${configurationFile} up ${detached}", - "contextType": "uplevel" + "contextType": "new" } ], "description": "%vscode-docker.config.template.composeUp.description%" @@ -1693,8 +1693,8 @@ "description": "%vscode-docker.config.template.contextType.description%", "enum": [ "all", - "downlevel", - "uplevel", + "legacy", + "new", "aci" ], "default": "all" @@ -1714,12 +1714,12 @@ { "label": "Compose Down", "template": "docker-compose ${configurationFile} down", - "contextType": "downlevel" + "contextType": "legacy" }, { "label": "Compose Down", "template": "docker compose ${configurationFile} down", - "contextType": "uplevel" + "contextType": "new" } ], "description": "%vscode-docker.config.template.composeDown.description%" diff --git a/package.nls.json b/package.nls.json index 3a0f888090..225f7c6e8e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -125,7 +125,7 @@ "vscode-docker.config.template.composeDown.label": "The label displayed to the user.", "vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.", "vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.", - "vscode-docker.config.template.contextType.description": "The context type in which the command applies. 'uplevel' includes all new contexts (currently, 'aci').", + "vscode-docker.config.template.contextType.description": "The context type in which the command applies. 'new' includes all new contexts (currently, 'aci').", "vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)", "vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None", "vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag", diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index c3825d7b48..c75225914e 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -5,12 +5,12 @@ import * as vscode from 'vscode'; import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui'; -import { ContextType, isUplevelContextType } from '../docker/Contexts'; +import { ContextType, isNewContextType } from '../docker/Contexts'; import { ext } from '../extensionVariables'; import { localize } from '../localize'; import { resolveVariables } from '../utils/resolveVariables'; -type TemplateContextType = 'all' | 'downlevel' | 'uplevel' | 'aci'; +type TemplateContextType = 'all' | 'legacy' | 'new' | 'aci'; type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown'; @@ -31,12 +31,12 @@ const defaults: { [key in TemplateCommand]: CommandTemplate[] } = { 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}', contextType: 'all' }], 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}', contextType: 'all' }], 'composeUp': [ - { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextType: 'downlevel' }, - { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', contextType: 'uplevel' }, + { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextType: 'legacy' }, + { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', contextType: 'new' }, ], 'composeDown': [ - { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextType: 'downlevel' }, - { label: 'Compose Down', template: 'docker compose ${configurationFile} down', contextType: 'uplevel' }, + { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextType: 'legacy' }, + { label: 'Compose Down', template: 'docker compose ${configurationFile} down', contextType: 'new' }, ], /* eslint-enable no-template-curly-in-string */ }; @@ -187,10 +187,10 @@ function currentContextTypeMatchesTemplate(currentContextType: ContextType, temp templateContextType = templateContextType || 'all'; switch (templateContextType) { - case 'uplevel': - return isUplevelContextType(currentContextType); - case 'downlevel': - return !isUplevelContextType(currentContextType); + case 'new': + return isNewContextType(currentContextType); + case 'legacy': + return !isNewContextType(currentContextType); case 'all': return true; case 'aci': diff --git a/src/docker/ContextManager.ts b/src/docker/ContextManager.ts index b0d8e02c66..c8609f9b74 100644 --- a/src/docker/ContextManager.ts +++ b/src/docker/ContextManager.ts @@ -16,7 +16,7 @@ import { LineSplitter } from '../debugging/coreclr/lineSplitter'; import { ext } from '../extensionVariables'; import { AsyncLazy } from '../utils/lazy'; import { execAsync, spawnAsync } from '../utils/spawnAsync'; -import { DockerContext, DockerContextInspection, isUplevelContextType } from './Contexts'; +import { DockerContext, DockerContextInspection, isNewContextType } from './Contexts'; import { DockerodeApiClient } from './DockerodeApiClient/DockerodeApiClient'; import { DockerServeClient } from './DockerServeClient/DockerServeClient'; @@ -112,7 +112,7 @@ export class DockerContextManager implements ContextManager, Disposable { void ext.dockerClient?.dispose(); // Create a new client - if (isUplevelContextType(currentContext.Type)) { + if (isNewContextType(currentContext.Type)) { // Currently vscode-docker:aciContext vscode-docker:newSdkContext mean the same thing // But that probably won't be true in the future, so define both as separate concepts now await this.setVsCodeContext('vscode-docker:aciContext', true); @@ -254,8 +254,8 @@ export class DockerContextManager implements ContextManager, Disposable { let result: boolean = false; const contexts = await this.contextsCache.getValue(); - if (contexts.some(c => isUplevelContextType(c.Type))) { - // If there are any uplevel contexts we automatically know it's the new CLI + if (contexts.some(c => isNewContextType(c.Type))) { + // If there are any new contexts we automatically know it's the new CLI result = true; } else { // Otherwise we look at the output of `docker serve --help` diff --git a/src/docker/Contexts.ts b/src/docker/Contexts.ts index 4af99b79dc..ae27ee0463 100644 --- a/src/docker/Contexts.ts +++ b/src/docker/Contexts.ts @@ -22,7 +22,7 @@ export interface DockerContextInspection { readonly [key: string]: unknown; } -export function isUplevelContextType(contextType: ContextType): boolean { +export function isNewContextType(contextType: ContextType): boolean { switch (contextType) { case 'moby': return false; From 41f596d9faa99f603407c9a2cedf32593944c5ef Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 11:01:15 -0400 Subject: [PATCH 08/15] Refactoring --- package.json | 154 +++++++++++++------------- package.nls.json | 2 +- src/commands/selectCommandTemplate.ts | 150 ++++++++++++++----------- 3 files changed, 166 insertions(+), 140 deletions(-) diff --git a/package.json b/package.json index e10b7e808b..ba3a7dadb3 100644 --- a/package.json +++ b/package.json @@ -1419,16 +1419,16 @@ "type": "string", "description": "%vscode-docker.config.template.build.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1462,16 +1462,16 @@ "type": "string", "description": "%vscode-docker.config.template.run.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1505,16 +1505,16 @@ "type": "string", "description": "%vscode-docker.config.template.runInteractive.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1548,16 +1548,16 @@ "type": "string", "description": "%vscode-docker.config.template.attach.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1591,16 +1591,16 @@ "type": "string", "description": "%vscode-docker.config.template.logs.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1634,16 +1634,16 @@ "type": "string", "description": "%vscode-docker.config.template.composeUp.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1660,12 +1660,13 @@ { "label": "Compose Up", "template": "docker-compose ${configurationFile} up ${detached} ${build}", - "contextType": "legacy" + "contextTypes": [ + "moby" + ] }, { "label": "Compose Up", - "template": "docker compose ${configurationFile} up ${detached}", - "contextType": "new" + "template": "docker compose ${configurationFile} up ${detached}" } ], "description": "%vscode-docker.config.template.composeUp.description%" @@ -1688,16 +1689,16 @@ "type": "string", "description": "%vscode-docker.config.template.composeDown.match%" }, - "contextType": { - "type": "string", - "description": "%vscode-docker.config.template.contextType.description%", - "enum": [ - "all", - "legacy", - "new", - "aci" - ], - "default": "all" + "contextTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "moby", + "aci" + ] + }, + "description": "%vscode-docker.config.template.contextTypes.description%" } }, "required": [ @@ -1714,12 +1715,13 @@ { "label": "Compose Down", "template": "docker-compose ${configurationFile} down", - "contextType": "legacy" + "contextTypes": [ + "moby" + ] }, { "label": "Compose Down", - "template": "docker compose ${configurationFile} down", - "contextType": "new" + "template": "docker compose ${configurationFile} down" } ], "description": "%vscode-docker.config.template.composeDown.description%" diff --git a/package.nls.json b/package.nls.json index 225f7c6e8e..fb3f026fd0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -125,7 +125,7 @@ "vscode-docker.config.template.composeDown.label": "The label displayed to the user.", "vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.", "vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.", - "vscode-docker.config.template.contextType.description": "The context type in which the command applies. 'new' includes all new contexts (currently, 'aci').", + "vscode-docker.config.template.contextTypes.description": "The context types in which the command template applies. If undefined, applies in all context types.", "vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)", "vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None", "vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag", diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index c75225914e..e8e1d537b6 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -5,38 +5,36 @@ import * as vscode from 'vscode'; import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui'; -import { ContextType, isNewContextType } from '../docker/Contexts'; +import { ContextType } from '../docker/Contexts'; import { ext } from '../extensionVariables'; import { localize } from '../localize'; import { resolveVariables } from '../utils/resolveVariables'; -type TemplateContextType = 'all' | 'legacy' | 'new' | 'aci'; - type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown'; type CommandTemplate = { template: string, label: string, match?: string, - contextType?: TemplateContextType, + contextTypes?: ContextType[], }; // NOTE: the default templates are duplicated in package.json, since VSCode offers no way of looking up extension-level default settings // So, when modifying them here, be sure to modify them there as well! const defaults: { [key in TemplateCommand]: CommandTemplate[] } = { /* eslint-disable no-template-curly-in-string */ - 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"', contextType: 'all' }], - 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}', contextType: 'all' }], - 'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}', contextType: 'all' }], - 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}', contextType: 'all' }], - 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}', contextType: 'all' }], + 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"' }], + 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}' }], + 'runInteractive': [{ label: 'Docker Run (Interactive)', template: 'docker run --rm -it ${exposedPorts} ${tag}' }], + 'attach': [{ label: 'Docker Attach', template: 'docker exec -it ${containerId} ${shellCommand}' }], + 'logs': [{ label: 'Docker Logs', template: 'docker logs -f ${containerId}' }], 'composeUp': [ - { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextType: 'legacy' }, - { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}', contextType: 'new' }, + { label: 'Compose Up', template: 'docker-compose ${configurationFile} up ${detached} ${build}', contextTypes: ['moby'] }, + { label: 'Compose Up', template: 'docker compose ${configurationFile} up ${detached}' }, ], 'composeDown': [ - { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextType: 'legacy' }, - { label: 'Compose Down', template: 'docker compose ${configurationFile} down', contextType: 'new' }, + { label: 'Compose Down', template: 'docker-compose ${configurationFile} down', contextTypes: ['moby'] }, + { label: 'Compose Down', template: 'docker compose ${configurationFile} down' }, ], /* eslint-enable no-template-curly-in-string */ }; @@ -98,65 +96,63 @@ export async function selectComposeCommand(context: IActionContext, folder: vsco ); } -async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext?: string[], folder?: vscode.WorkspaceFolder, additionalVariables?: { [key: string]: string }): Promise { +async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext: string[], folder: vscode.WorkspaceFolder | undefined, additionalVariables: { [key: string]: string }): Promise { // Get the current context type const currentContext = await ext.dockerContextManager.getCurrentContext(); const currentContextType = currentContext.Type; - // Get the templates from settings + // Get the configured settings values const config = vscode.workspace.getConfiguration('docker'); const templateSetting: CommandTemplate[] | string = config.get(`commands.${command}`); - let templates: CommandTemplate[]; + let settingsTemplates: CommandTemplate[]; - // Get template(s) from settings + // Get a template array from settings if (typeof (templateSetting) === 'string') { - templates = [{ template: templateSetting }] as CommandTemplate[]; + settingsTemplates = [{ template: templateSetting }] as CommandTemplate[]; } else if (!templateSetting) { // If templateSetting is some falsy value, make this an empty array so the hardcoded default above gets used - templates = []; + settingsTemplates = []; } else { - templates = templateSetting; + settingsTemplates = templateSetting; } - // Look for settings-defined template(s) with explicit match, that matches the match context and the current Docker context type - const matchedTemplates = templates.filter(template => { - if (!currentContextTypeMatchesTemplate(currentContextType, template.contextType)) { - return false; - } + // Get a template array from hardcoded defaults + const hardcodedTemplates = defaults[command]; - if (template.match) { - try { - const matcher = new RegExp(template.match, 'i'); - return matchContext.some(m => matcher.test(m)); - } catch { - // Don't wait - // eslint-disable-next-line @typescript-eslint/no-floating-promises - ext.ui.showWarningMessage(localize('vscode-docker.commands.selectCommandTemplate.invalidMatch', 'Invalid match expression for template \'{0}\'. This template will be skipped.', template.label)); - } - } + // Build the template selection matrix. Settings-defined values are preferred over hardcoded, and constrained over unconstrained. + // Constrained templates have either `match` or `contextTypes`, and must match the constraints. + // Unconstrained templates have neither `match` nor `contextTypes`. + const templateMatrix: CommandTemplate[][] = []; - return false; - }); + // 0. Settings-defined templates with either `match` or `contextTypes`, that satisfy the constraints + templateMatrix.push(getConstrainedTemplates(settingsTemplates, matchContext, currentContextType)); + + // 1. Settings-defined templates with neither `match` nor `contextTypes` + templateMatrix.push(getUnconstrainedTemplates(settingsTemplates)); - // Look for settings-defined template(s) with no explicit match and the current Docker context type - const universalTemplates = templates.filter(template => !template.match && currentContextTypeMatchesTemplate(currentContextType, template.contextType)); + // 2. Hardcoded templates with either `match` or `contextTypes`, that satisfy the constraints + templateMatrix.push(getConstrainedTemplates(hardcodedTemplates, matchContext, currentContextType)); - // Get the default templates from code above that match the current context (hopefully just one) - const defaultCommandsForContext = defaults[command].filter(template => currentContextTypeMatchesTemplate(currentContextType, template.contextType)); + // 3. Hardcoded templates with neither `match` nor `contextTypes` + templateMatrix.push(getUnconstrainedTemplates(hardcodedTemplates)); - // Select from explicit match templates, if none then from settings-defined universal templates, if none then hardcoded default + // Select the template to use let selectedTemplate: CommandTemplate; - if (matchedTemplates.length > 0) { - selectedTemplate = await quickPickTemplate(context, matchedTemplates); - } else if (universalTemplates.length > 0) { - selectedTemplate = await quickPickTemplate(context, universalTemplates); - } else { - selectedTemplate = await quickPickTemplate(context, defaultCommandsForContext); + for (const templates of templateMatrix) { + // Skip any empty group + if (templates.length === 0) { + continue; + } + + // Choose a template from the first non-empty group + // If only one matches there will be no prompt + selectedTemplate = await quickPickTemplate(context, templates); + break; } - context.telemetry.properties.isDefaultCommand = defaultCommandsForContext.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; + context.telemetry.properties.isDefaultCommand = hardcodedTemplates.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; - context.telemetry.properties.commandContextType = selectedTemplate.contextType ?? 'all'; + context.telemetry.properties.commandContextType = `[${selectedTemplate.contextTypes?.join(', ') ?? ''}]`; context.telemetry.properties.currentContextType = currentContextType; return resolveVariables(selectedTemplate.template, folder, additionalVariables); @@ -183,18 +179,46 @@ async function quickPickTemplate(context: IActionContext, templates: CommandTemp return selection.data; } -function currentContextTypeMatchesTemplate(currentContextType: ContextType, templateContextType: TemplateContextType | undefined): boolean { - templateContextType = templateContextType || 'all'; - - switch (templateContextType) { - case 'new': - return isNewContextType(currentContextType); - case 'legacy': - return !isNewContextType(currentContextType); - case 'all': - return true; - case 'aci': - default: // Using this code as the default means unknown values can still be made to work - return currentContextType === templateContextType; +function getConstrainedTemplates(templates: CommandTemplate[], matchContext: string[], currentContextType: ContextType): CommandTemplate[] { + return templates.filter(template => { + if (!template.contextTypes && !template.match) { + // If neither contextTypes nor match is defined, this is an unconstrained template + return false; + } + + + return isContextTypeConstraintSatisfied(currentContextType, template.contextTypes) && + isMatchConstraintSatisfied(matchContext, template.match); + }); +} + +function getUnconstrainedTemplates(templates: CommandTemplate[]): CommandTemplate[] { + return templates.filter(template => { + // Both contextTypes and match must be falsy to make this an unconstrained template + return !template.contextTypes && !template.match; + }); +} + +function isContextTypeConstraintSatisfied(currentContextType: ContextType, templateContextTypes: ContextType[] | undefined): boolean { + if (!templateContextTypes) { + // If templateContextTypes is undefined or empty, it is automatically satisfied + return true; + } + + return templateContextTypes.some(tc => tc === currentContextType); +} + +function isMatchConstraintSatisfied(matchContext: string[], match: string | undefined): boolean { + if (!match) { + // If match is undefined or empty, it is automatically satisfied + return true; + } + + try { + const matcher = new RegExp(match, 'i'); + return matchContext.some(m => matcher.test(m)); + } catch { + // Don't wait + void ext.ui.showWarningMessage(localize('vscode-docker.commands.selectCommandTemplate.invalidMatch', 'Invalid match expression \'{0}\'. This template will be skipped.', match)); } } From 80c8c083da57e8e213b52d8e631a49b355ae5532 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 11:07:10 -0400 Subject: [PATCH 09/15] Throw an error --- src/commands/selectCommandTemplate.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index e8e1d537b6..d4ece4e77f 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -150,6 +150,10 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC break; } + if (!selectedTemplate) { + throw new Error(localize('vscode-docker.commands.selectCommandTemplate.noTemplate', 'No command template was found for command \'{0}\'', command)); + } + context.telemetry.properties.isDefaultCommand = hardcodedTemplates.some(t => t.template === selectedTemplate.template) ? 'true' : 'false'; context.telemetry.properties.isCommandRegexMatched = selectedTemplate.match ? 'true' : 'false'; context.telemetry.properties.commandContextType = `[${selectedTemplate.contextTypes?.join(', ') ?? ''}]`; From e6e49189ec27b48f50cf0988a2daa7fa7d68016a Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:38:28 -0400 Subject: [PATCH 10/15] Add unit tests for command template selection --- extension.bundle.ts | 2 + src/commands/selectCommandTemplate.ts | 14 +- test/commands/selectCommandTemplate.test.ts | 558 ++++++++++++++++++++ test/runWithSetting.ts | 8 +- 4 files changed, 572 insertions(+), 10 deletions(-) create mode 100644 test/commands/selectCommandTemplate.test.ts diff --git a/extension.bundle.ts b/extension.bundle.ts index 9941ab7282..9de6466297 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -48,9 +48,11 @@ export { DebugConfigurationBase } from './src/debugging/DockerDebugConfiguration export { ActivityMeasurementService } from './src/telemetry/ActivityMeasurementService'; export { ExperimentationTelemetry } from './src/telemetry/ExperimentationTelemetry'; export { DockerApiClient } from './src/docker/DockerApiClient'; +export { DockerContext, isNewContextType } from './src/docker/Contexts'; export { DockerContainer } from './src/docker/Containers'; export { DockerImage } from './src/docker/Images'; export { DockerNetwork } from './src/docker/Networks'; export { DockerVolume } from './src/docker/Volumes'; +export { CommandTemplate, selectCommandTemplate, defaultCommandTemplates } from './src/commands/selectCommandTemplate'; export * from 'vscode-azureextensionui'; diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index d4ece4e77f..90db6d8324 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -12,7 +12,8 @@ import { resolveVariables } from '../utils/resolveVariables'; type TemplateCommand = 'build' | 'run' | 'runInteractive' | 'attach' | 'logs' | 'composeUp' | 'composeDown'; -type CommandTemplate = { +// Exported only for tests +export type CommandTemplate = { template: string, label: string, match?: string, @@ -21,7 +22,8 @@ type CommandTemplate = { // NOTE: the default templates are duplicated in package.json, since VSCode offers no way of looking up extension-level default settings // So, when modifying them here, be sure to modify them there as well! -const defaults: { [key in TemplateCommand]: CommandTemplate[] } = { +// Exported only for tests +export const defaultCommandTemplates: { [key in TemplateCommand]: CommandTemplate[] } = { /* eslint-disable no-template-curly-in-string */ 'build': [{ label: 'Docker Build', template: 'docker build --pull --rm -f "${dockerfile}" -t ${tag} "${context}"' }], 'run': [{ label: 'Docker Run', template: 'docker run --rm -d ${exposedPorts} ${tag}' }], @@ -96,10 +98,10 @@ export async function selectComposeCommand(context: IActionContext, folder: vsco ); } -async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext: string[], folder: vscode.WorkspaceFolder | undefined, additionalVariables: { [key: string]: string }): Promise { +// Exported only for tests +export async function selectCommandTemplate(context: IActionContext, command: TemplateCommand, matchContext: string[], folder: vscode.WorkspaceFolder | undefined, additionalVariables: { [key: string]: string }): Promise { // Get the current context type - const currentContext = await ext.dockerContextManager.getCurrentContext(); - const currentContextType = currentContext.Type; + const currentContextType = (await ext.dockerContextManager.getCurrentContext()).Type; // Get the configured settings values const config = vscode.workspace.getConfiguration('docker'); @@ -117,7 +119,7 @@ async function selectCommandTemplate(context: IActionContext, command: TemplateC } // Get a template array from hardcoded defaults - const hardcodedTemplates = defaults[command]; + const hardcodedTemplates = defaultCommandTemplates[command]; // Build the template selection matrix. Settings-defined values are preferred over hardcoded, and constrained over unconstrained. // Constrained templates have either `match` or `contextTypes`, and must match the constraints. diff --git a/test/commands/selectCommandTemplate.test.ts b/test/commands/selectCommandTemplate.test.ts new file mode 100644 index 0000000000..3f8a1b00c4 --- /dev/null +++ b/test/commands/selectCommandTemplate.test.ts @@ -0,0 +1,558 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { runWithSetting } from '../runWithSetting'; +import { CommandTemplate, selectCommandTemplate, defaultCommandTemplates, ext, DockerContext } from '../../extension.bundle'; +import { TestInput } from 'vscode-azureextensiondev'; +import { IActionContext } from 'vscode-azureextensionui'; +import { testUserInput } from '../global.test'; +import assert = require('assert'); +import { isNewContextType } from '../../src/docker/Contexts'; + +suite("(unit) selectCommandTemplate", () => { + test("One constrained from settings (match)", async () => { + const result = await runWithCommandSetting( + [ + { + // *Satisfied constraint (match) + label: 'test', + template: 'test', + match: 'test', + }, + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unconstrained + label: 'fail2', + template: 'fail', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("One constrained from settings (contextTypes)", async () => { + const result = await runWithCommandSetting( + [ + { + // *Satisfied constraint (contextTypes + match) + label: 'test', + template: 'test', + match: 'test', + contextTypes: ['moby', 'aci'], + }, + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + contextTypes: ['moby', 'aci'], + }, + { + // Unconstrained + label: 'fail2', + template: 'fail', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Two constrained from settings", async () => { + const result = await runWithCommandSetting( + [ + { + // *Satisfied constraint (contextTypes) + label: 'test', + template: 'test', + contextTypes: ['moby'], + }, + { + // *Satisfied constraint (match) + label: 'test2', + template: 'test', + match: 'test', + }, + { + // Unconstrained + label: 'fail', + template: 'fail', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail2', + template: 'fail', + }, + ], + [TestInput.UseDefaultValue], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("One unconstrained from settings", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + { + // *Unconstrained + label: 'test', + template: 'test', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Two unconstrained from settings", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + { + // *Unconstrained + label: 'test', + template: 'test', + }, + { + // *Unconstrained + label: 'test2', + template: 'test', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [TestInput.UseDefaultValue], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("One constrained from hardcoded (match)", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + ], + [ + { + // *Satisfied constraint (match) hardcoded + label: 'test', + template: 'test', + match: 'test', + }, + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("One constrained from hardcoded (contextTypes)", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + ], + [ + { + // *Satisfied constraint (contextTypes + match) hardcoded + label: 'test', + template: 'test', + match: 'test', + contextTypes: ['moby'], + }, + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Two constrained from hardcoded", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + ], + [ + { + // *Satisfied constraint (contextTypes + match) hardcoded + label: 'test', + template: 'test', + match: 'test', + contextTypes: ['moby'], + }, + { + // *Satisfied constraint (match) hardcoded + label: 'test2', + template: 'test', + match: 'test', + }, + { + // Unconstrained hardcoded + label: 'fail3', + template: 'fail', + }, + ], + [TestInput.UseDefaultValue], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("One unconstrained from hardcoded", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + ], + [ + { + // Unsatisfied constraint (match) hardcoded + label: 'fail3', + template: 'fail', + match: 'fail', + contextTypes: ['moby'], + }, + { + // Unsatisfied constraint (contextTypes) hardcoded + label: 'fail4', + template: 'fail', + contextTypes: ['aci'] + }, + { + // *Unconstrained hardcoded + label: 'test', + template: 'test', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Two unconstrained from hardcoded", async () => { + const result = await runWithCommandSetting( + [ + { + // Unsatisfied constraint (match) + label: 'fail', + template: 'fail', + match: 'fail', + }, + { + // Unsatisfied constraint (contextTypes) + label: 'fail2', + template: 'fail', + contextTypes: ['aci'], + }, + ], + [ + { + // Unsatisfied constraint (match) hardcoded + label: 'fail3', + template: 'fail', + match: 'fail', + contextTypes: ['moby'], + }, + { + // Unsatisfied constraint (contextTypes) hardcoded + label: 'fail4', + template: 'fail', + contextTypes: ['aci'] + }, + { + // *Unconstrained hardcoded + label: 'test', + template: 'test', + }, + { + // *Unconstrained hardcoded + label: 'test2', + template: 'test', + }, + ], + [TestInput.UseDefaultValue], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Setting is a string", async () => { + const result = await runWithCommandSetting( + // *String setting + 'test', + [ + { + // Unconstrained hardcoded + label: 'fail', + template: 'fail', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Setting is falsy", async () => { + const result = await runWithCommandSetting( + [], // Falsy setting + [ + { + // *Unconstrained hardcoded + label: 'test', + template: 'test', + }, + ], + [], + 'moby', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Unknown context constrained", async () => { + const result = await runWithCommandSetting( + [ + { + // *Satisfied constraint (match) + label: 'test', + template: 'test', + match: 'test', + }, + { + // Unconstrained + label: 'fail', + template: 'fail', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail2', + template: 'fail', + }, + ], + [], + 'abc', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + }); + + test("Unknown context unconstrained", async () => { + const result = await runWithCommandSetting( + [ + { + // *Unconstrained + label: 'test', + template: 'test', + }, + ], + [ + { + // Unconstrained hardcoded + label: 'fail', + template: 'fail', + }, + ], + [], + 'abc', + ['test'] + ); + + assert.equal(result.command, 'test', 'Incorrect command selected'); + + // Quick aside: validate that the context manager thinks an unknown context is new + assert.equal(isNewContextType('abc' as any), true, 'Incorrect context type identification'); + }); +}); + +async function runWithCommandSetting( + settingsValues: CommandTemplate[] | string, + hardcodedValues: CommandTemplate[], + pickInputs: TestInput[], + contextType: string, + matchContext: string[]): Promise<{ command: string, context: IActionContext }> { + + const oldDefaultTemplates = defaultCommandTemplates['build']; + defaultCommandTemplates['build'] = hardcodedValues; + + const oldContextManager = ext.dockerContextManager; + ext.dockerContextManager = { + onContextChanged: undefined, + refresh: undefined, + getContexts: undefined, + inspect: undefined, + use: undefined, + remove: undefined, + isNewCli: undefined, + + // Only getCurrentContext is called by selectCommandTemplate + // From it, only Type is used + getCurrentContext: async () => { + return { + Type: contextType, + } as DockerContext; + }, + }; + + try { + const tempContext: IActionContext = { + telemetry: { properties: {}, measurements: {}, }, + errorHandling: { issueProperties: {}, }, + }; + + const cmdResult: string = await runWithSetting('commands.build', settingsValues, async () => { + return await testUserInput.runWithInputs(pickInputs, async () => { + return await selectCommandTemplate(tempContext, 'build', matchContext, undefined, {}); + }); + }); + + return { + command: cmdResult, + context: tempContext, + }; + } finally { + defaultCommandTemplates['build'] = oldDefaultTemplates; + ext.dockerContextManager = oldContextManager; + } +} diff --git a/test/runWithSetting.ts b/test/runWithSetting.ts index 41aa3a9b8f..01856e3970 100644 --- a/test/runWithSetting.ts +++ b/test/runWithSetting.ts @@ -6,13 +6,13 @@ import { ConfigurationTarget, workspace, WorkspaceConfiguration } from "vscode"; import { configPrefix } from "../extension.bundle"; -export async function runWithSetting(key: string, value: T | undefined, callback: () => Promise): Promise { +export async function runWithSetting(key: string, value: TSetting | undefined, callback: () => Promise): Promise { const config: WorkspaceConfiguration = workspace.getConfiguration(configPrefix); - const result = config.inspect(key); - const oldValue: T | undefined = result && result.globalValue; + const result = config.inspect(key); + const oldValue: TSetting | undefined = result && result.globalValue; try { await config.update(key, value, ConfigurationTarget.Global); - await callback(); + return await callback(); } finally { await config.update(key, oldValue, ConfigurationTarget.Global); } From 4b55b6513b82e5eac82e2e45ee186e4e6800d93d Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:52:04 -0400 Subject: [PATCH 11/15] Validate telemetry properties too --- test/commands/selectCommandTemplate.test.ts | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/commands/selectCommandTemplate.test.ts b/test/commands/selectCommandTemplate.test.ts index 3f8a1b00c4..340b50a595 100644 --- a/test/commands/selectCommandTemplate.test.ts +++ b/test/commands/selectCommandTemplate.test.ts @@ -39,6 +39,12 @@ suite("(unit) selectCommandTemplate", () => { label: 'fail3', template: 'fail', }, + { + // Unconstrained hardcoded (value is test to assert isDefaultCommand == true) + // (If we try to choose here it will fail due to prompting unexpectedly) + label: 'fail4', + template: 'test', + } ], [], 'moby', @@ -46,6 +52,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("One constrained from settings (contextTypes)", async () => { @@ -84,6 +94,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[moby, aci]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Two constrained from settings", async () => { @@ -120,6 +134,8 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("One unconstrained from settings", async () => { @@ -156,6 +172,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Two unconstrained from settings", async () => { @@ -197,6 +217,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("One constrained from hardcoded (match)", async () => { @@ -234,6 +258,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("One constrained from hardcoded (contextTypes)", async () => { @@ -272,6 +300,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[moby]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Two constrained from hardcoded", async () => { @@ -316,6 +348,9 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("One unconstrained from hardcoded", async () => { @@ -360,6 +395,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Two unconstrained from hardcoded", async () => { @@ -409,6 +448,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Setting is a string", async () => { @@ -428,6 +471,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Setting is falsy", async () => { @@ -446,6 +493,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'true', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'moby', 'Wrong value for currentContextType'); }); test("Unknown context constrained", async () => { @@ -476,6 +527,10 @@ suite("(unit) selectCommandTemplate", () => { ); assert.equal(result.command, 'test', 'Incorrect command selected'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'true', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'abc', 'Wrong value for currentContextType'); }); test("Unknown context unconstrained", async () => { @@ -503,6 +558,10 @@ suite("(unit) selectCommandTemplate", () => { // Quick aside: validate that the context manager thinks an unknown context is new assert.equal(isNewContextType('abc' as any), true, 'Incorrect context type identification'); + assert.equal(result.context.telemetry.properties.isDefaultCommand, 'false', 'Wrong value for isDefaultCommand'); + assert.equal(result.context.telemetry.properties.isCommandRegexMatched, 'false', 'Wrong value for isCommandRegexMatched'); + assert.equal(result.context.telemetry.properties.commandContextType, '[]', 'Wrong value for commandContextType'); + assert.equal(result.context.telemetry.properties.currentContextType, 'abc', 'Wrong value for currentContextType'); }); }); From d6b848dd05f6e4a4831d4e1fecee6311879b9daa Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 14:11:18 -0400 Subject: [PATCH 12/15] Update vscode-azureextensiondev to make tests work --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 50bed25952..c3bb68f454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11801,9 +11801,9 @@ } }, "vscode-azureextensiondev": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/vscode-azureextensiondev/-/vscode-azureextensiondev-0.4.0.tgz", - "integrity": "sha512-2Ztr9UmO/AiY4Sy9nlXsQMUfVO6OZtN8eUyqQS+9krgGohPdNbdoOlYkwqWWwc/aAK6M263Lf6gZcv6NCqLxpQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vscode-azureextensiondev/-/vscode-azureextensiondev-0.4.1.tgz", + "integrity": "sha512-uQul8jKKOexMN7SJNTNm0YF93+xbKtxrgUm6fJFymk8iddE7R86K7dPOq9+VjvwUPMSQirZLYIE2PCRRCIXsoA==", "dev": true, "requires": { "azure-arm-resource": "^3.0.0-preview", @@ -12184,9 +12184,9 @@ } }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true, "optional": true }, @@ -12201,9 +12201,9 @@ } }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", "dev": true, "optional": true, "requires": { diff --git a/package.json b/package.json index ba3a7dadb3..e6c046a788 100644 --- a/package.json +++ b/package.json @@ -2737,7 +2737,7 @@ "typescript": "^3.9.7", "umd-compat-loader": "^2.1.2", "vsce": "^1.77.0", - "vscode-azureextensiondev": "^0.4.0", + "vscode-azureextensiondev": "^0.4.1", "vscode-nls-dev": "^3.3.2", "vscode-test": "^1.4.0", "webpack": "^4.43.0", From ea784bad19ba29a13bc7f8678495299e24421880 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 22 Jul 2020 14:16:55 -0400 Subject: [PATCH 13/15] Fix test failure --- test/commands/selectCommandTemplate.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/commands/selectCommandTemplate.test.ts b/test/commands/selectCommandTemplate.test.ts index 340b50a595..a5218a7e30 100644 --- a/test/commands/selectCommandTemplate.test.ts +++ b/test/commands/selectCommandTemplate.test.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { runWithSetting } from '../runWithSetting'; -import { CommandTemplate, selectCommandTemplate, defaultCommandTemplates, ext, DockerContext } from '../../extension.bundle'; +import { CommandTemplate, selectCommandTemplate, defaultCommandTemplates, ext, DockerContext, isNewContextType } from '../../extension.bundle'; import { TestInput } from 'vscode-azureextensiondev'; import { IActionContext } from 'vscode-azureextensionui'; import { testUserInput } from '../global.test'; import assert = require('assert'); -import { isNewContextType } from '../../src/docker/Contexts'; suite("(unit) selectCommandTemplate", () => { test("One constrained from settings (match)", async () => { From dce88d0bdd1bf4a499b455a3badac6fe20460395 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 23 Jul 2020 11:19:19 -0400 Subject: [PATCH 14/15] Phrasing change --- package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index fb3f026fd0..ab3dfb3565 100644 --- a/package.nls.json +++ b/package.nls.json @@ -125,7 +125,7 @@ "vscode-docker.config.template.composeDown.label": "The label displayed to the user.", "vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.", "vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.", - "vscode-docker.config.template.contextTypes.description": "The context types in which the command template applies. If undefined, applies in all context types.", + "vscode-docker.config.template.contextTypes.description": "The context types in which the command template applies. If undefined or empty, the template applies in all context types.", "vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)", "vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None", "vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag", From 18150046599c489b0d5ab9a49220ac9008c1dcec Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 23 Jul 2020 11:22:56 -0400 Subject: [PATCH 15/15] Minor fixes --- src/commands/selectCommandTemplate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 90db6d8324..c03da2c25a 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -192,7 +192,6 @@ function getConstrainedTemplates(templates: CommandTemplate[], matchContext: str return false; } - return isContextTypeConstraintSatisfied(currentContextType, template.contextTypes) && isMatchConstraintSatisfied(matchContext, template.match); }); @@ -227,4 +226,6 @@ function isMatchConstraintSatisfied(matchContext: string[], match: string | unde // Don't wait void ext.ui.showWarningMessage(localize('vscode-docker.commands.selectCommandTemplate.invalidMatch', 'Invalid match expression \'{0}\'. This template will be skipped.', match)); } + + return false; }