Skip to content

Commit

Permalink
Cherry pick several things for 0.8.2 release (#1367)
Browse files Browse the repository at this point in the history
* Update package

* Cherry pick clearer error message for duplicate registry

* Cherry pick improved Dockerfile pattern matches, save event telemetry

* Cherry pick only refresh if VSCode is in focus

* Cherry pick add a command to create the simplest network

* Cherry pick ask some active users to take NPS survey
  • Loading branch information
bwateratmsft authored Oct 24, 2019
1 parent 9125a19 commit f3a3e9f
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 12 deletions.
19 changes: 15 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 23 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"onCommand:vscode-docker.images.runInteractive",
"onCommand:vscode-docker.images.tag",
"onCommand:vscode-docker.networks.configureExplorer",
"onCommand:vscode-docker.networks.create",
"onCommand:vscode-docker.networks.inspect",
"onCommand:vscode-docker.networks.prune",
"onCommand:vscode-docker.networks.refresh",
Expand Down Expand Up @@ -213,10 +214,15 @@
"group": "navigation@9"
},
{
"command": "vscode-docker.networks.prune",
"command": "vscode-docker.networks.create",
"when": "view == dockerNetworks",
"group": "navigation@1"
},
{
"command": "vscode-docker.networks.prune",
"when": "view == dockerNetworks",
"group": "navigation@2"
},
{
"command": "vscode-docker.networks.refresh",
"when": "view == dockerNetworks",
Expand Down Expand Up @@ -684,7 +690,11 @@
],
"filenamePatterns": [
"*.dockerfile",
"Dockerfile"
"Dockerfile",
"Dockerfile.debug",
"Dockerfile.dev",
"Dockerfile.develop",
"Dockerfile.prod"
]
},
{
Expand All @@ -704,8 +714,8 @@
},
"docker.explorerRefreshInterval": {
"type": "number",
"default": 1000,
"description": "Explorer refresh interval, default is 1000ms"
"default": 2000,
"description": "Explorer refresh interval, default is 2000ms"
},
"docker.containers.groupBy": {
"type": "string",
Expand Down Expand Up @@ -1291,6 +1301,15 @@
"dark": "resources/dark/settings.svg"
}
},
{
"command": "vscode-docker.networks.create",
"title": "Create...",
"category": "Docker Networks",
"icon": {
"light": "resources/light/add.svg",
"dark": "resources/dark/add.svg"
}
},
{
"command": "vscode-docker.networks.inspect",
"title": "Inspect",
Expand Down
3 changes: 3 additions & 0 deletions resources/dark/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions resources/light/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions src/commands/networks/createNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { window } from 'vscode';
import { IActionContext } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';

export async function createNetwork(_context: IActionContext): Promise<void> {

const name = await ext.ui.showInputBox({
value: '',
prompt: 'Name of the network'
});

const driverSelection = await ext.ui.showQuickPick(
[
{ label: 'bridge' },
{ label: 'host' },
{ label: 'overlay' },
{ label: 'macvlan' }
],
{
canPickMany: false,
placeHolder: 'Select the network driver to use (default is "bridge").'
}
);

const result = <{ id: string }>await ext.dockerode.createNetwork({ Name: name, Driver: driverSelection.label });

window.showInformationMessage(`Network Created with ID ${result.id.substr(0, 12)}`);
}
2 changes: 2 additions & 0 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { runAzureCliImage } from "./images/runAzureCliImage";
import { runImage, runImageInteractive } from "./images/runImage";
import { tagImage } from "./images/tagImage";
import { configureNetworksExplorer } from "./networks/configureNetworksExplorer";
import { createNetwork } from "./networks/createNetwork";
import { inspectNetwork } from "./networks/inspectNetwork";
import { pruneNetworks } from "./networks/pruneNetworks";
import { removeNetwork } from "./networks/removeNetwork";
Expand Down Expand Up @@ -86,6 +87,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.images.tag', tagImage);

registerCommand('vscode-docker.networks.configureExplorer', configureNetworksExplorer);
registerCommand('vscode-docker.networks.create', createNetwork);
registerCommand('vscode-docker.networks.inspect', inspectNetwork);
registerCommand('vscode-docker.networks.remove', removeNetwork);
registerCommand('vscode-docker.networks.prune', pruneNetworks);
Expand Down
8 changes: 8 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo';
import { DockerComposeParser } from './dockerCompose/dockerComposeParser';
import { DockerfileCompletionItemProvider } from './dockerfileCompletionItemProvider';
import { ext } from './extensionVariables';
import { registerListeners } from './registerListeners';
import { registerTrees } from './tree/registerTrees';
import { addDockerSettingsToEnv } from './utils/addDockerSettingsToEnv';
import { addUserAgent } from './utils/addUserAgent';
import { getTrustedCertificates } from './utils/getTrustedCertificates';
import { Keytar } from './utils/keytar';
import { nps } from './utils/nps';
import { DefaultTerminalProvider } from './utils/TerminalProvider';
import { wrapError } from './utils/wrapError';

Expand Down Expand Up @@ -122,6 +124,12 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats:

await consolidateDefaultRegistrySettings();
activateLanguageClient();

registerListeners(ctx);

// Don't wait
// tslint:disable-next-line: no-floating-promises
nps(ctx.globalState);
});
}

Expand Down
24 changes: 24 additions & 0 deletions src/registerListeners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ExtensionContext, TextDocument, workspace } from 'vscode';
import { ext } from './extensionVariables';

let lastUploadTime: number = 0;
const hourInMilliseconds = 1000 * 60 * 60;

export function registerListeners(ctx: ExtensionContext): void {
ctx.subscriptions.push(workspace.onDidSaveTextDocument(onDidSaveTextDocument));
}

function onDidSaveTextDocument(doc: TextDocument): void {
// If it's not a Dockerfile, or last upload time is within an hour, skip
if (doc.languageId !== 'dockerfile' || lastUploadTime + hourInMilliseconds > Date.now()) {
return;
}

lastUploadTime = Date.now();
ext.reporter.sendTelemetryEvent('dockerfilesave', { "lineCount": doc.lineCount.toString() }, {});
}
6 changes: 3 additions & 3 deletions src/tree/LocalRootTreeItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ConfigurationChangeEvent, ConfigurationTarget, TreeView, TreeViewVisibilityChangeEvent, workspace, WorkspaceConfiguration } from "vscode";
import { ConfigurationChangeEvent, ConfigurationTarget, TreeView, TreeViewVisibilityChangeEvent, window, workspace, WorkspaceConfiguration } from "vscode";
import { AzExtParentTreeItem, AzExtTreeItem, AzureWizard, GenericTreeItem, IActionContext, InvalidTreeItem, registerEvent } from "vscode-azureextensionui";
import { configPrefix } from "../constants";
import { ext } from "../extensionVariables";
Expand Down Expand Up @@ -74,10 +74,10 @@ export abstract class LocalRootTreeItemBase<TItem extends ILocalItem, TProperty

if (e.visible) {
const configOptions: WorkspaceConfiguration = workspace.getConfiguration('docker');
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 2000);
intervalId = setInterval(
async () => {
if (await this.hasChanged()) {
if (window.state.focused && await this.hasChanged()) {
await this.refresh();
}
},
Expand Down
6 changes: 5 additions & 1 deletion src/tree/registries/RegistriesTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ export class RegistriesTreeItem extends AzExtParentTreeItem {
context.telemetry.properties.cancelStep = 'learnHowToContribute';
throw new UserCancelledError();
} else if (provider.onlyOneAllowed && this._cachedProviders.find(c => c.id === provider.id)) {
throw new Error(`Only one provider with id "${provider.id}" is allowed at a time.`);
// Don't wait, no input to wait for anyway
// tslint:disable-next-line: no-floating-promises
ext.ui.showWarningMessage(`The "${provider.label}" registry provider is already connected.`);
context.telemetry.properties.cancelStep = 'registryProviderAlreadyAdded';
throw new UserCancelledError();
}

context.telemetry.properties.providerId = provider.id;
Expand Down
85 changes: 85 additions & 0 deletions src/utils/nps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// Loosely adapted from https://github.com/microsoft/vscode-azure-account/blob/2f497562cab5f3db09f983ab5101040f27dceb70/src/nps.ts

import { env, Memento, Uri, window } from "vscode";
import { ext } from "vscode-azureappservice/out/src/extensionVariables";

const PROBABILITY = 0.15;
const MIN_SESSION_COUNT = 10;

const SURVEY_NAME = 'nps1';
const SURVEY_URL = 'https://aka.ms/vscodedockernpsinproduct';

const SESSION_COUNT_KEY = `${SURVEY_NAME}/sessioncount`;
const LAST_SESSION_DATE_KEY = `${SURVEY_NAME}/lastsessiondate`;
const IS_CANDIDATE_KEY = `${SURVEY_NAME}/iscandidate`;

export async function nps(globalState: Memento): Promise<void> {
try {
// If not English-language, don't ask
if (env.language !== 'en' && !env.language.startsWith('en-')) {
return;
}

let isCandidate: boolean | undefined = globalState.get(IS_CANDIDATE_KEY);

// If not a candidate, don't ask
if (isCandidate === false) {
return;
}

const date = new Date().toDateString();
const lastSessionDate = globalState.get(LAST_SESSION_DATE_KEY, new Date(0).toDateString());

// If this session is on same date as last session, don't count it
if (date === lastSessionDate) {
return;
}

// Count this session
const sessionCount = globalState.get(SESSION_COUNT_KEY, 0) + 1;
await globalState.update(LAST_SESSION_DATE_KEY, date);
await globalState.update(SESSION_COUNT_KEY, sessionCount);

// If under the MIN_SESSION_COUNT, don't ask
if (sessionCount < MIN_SESSION_COUNT) {
return;
}

// Decide if they are a candidate (if we previously decided they are and they did Remind Me Later, we will not do probability again)
// i.e. Probability only comes into play if isCandidate is undefined
// tslint:disable-next-line: insecure-random
isCandidate = isCandidate || Math.random() < PROBABILITY;
await globalState.update(IS_CANDIDATE_KEY, isCandidate);

// If not a candidate, don't ask
if (!isCandidate) {
return;
}

const take = { title: 'Take Survey', telName: 'take' };
const remind = { title: 'Remind Me Later', telName: 'remind' };
const never = { title: 'Don\'t Show Again', telName: 'never' };

// Prompt, treating hitting X as Remind Me Later
const result = (await window.showInformationMessage('Do you mind taking a quick feedback survey about the Docker Extension for VS Code?', take, remind, never)) || remind;

ext.reporter.sendTelemetryEvent('nps', { survey: SURVEY_NAME, response: result.telName });

if (result === take) {
// If they hit Take, don't ask again (for this survey name), and open the survey
await globalState.update(IS_CANDIDATE_KEY, false);
await env.openExternal(Uri.parse(`${SURVEY_URL}?o=${encodeURIComponent(process.platform)}&m=${encodeURIComponent(env.machineId)}`));
} else if (result === remind) {
// If they hit the X or Remind Me Later, ask again in 3 sessions
await globalState.update(SESSION_COUNT_KEY, MIN_SESSION_COUNT - 3);
} else if (result === never) {
// If they hit Never, don't ask again (for this survey name)
await globalState.update(IS_CANDIDATE_KEY, false);
}
} catch { } // Best effort
}

0 comments on commit f3a3e9f

Please sign in to comment.