Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27529bfc33 | ||
|
|
0e4ae83e74 | ||
|
|
3b1ff0f4a3 | ||
|
|
5079abd06f | ||
|
|
4e94f70e6f | ||
|
|
79e2666586 | ||
|
|
02080cd797 | ||
|
|
7347ff5512 |
@@ -94,7 +94,7 @@ Alternatively, you can run the tests inside of vscode. There are several vscode
|
||||
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
|
||||
* Go through all recent PRs and make sure they are properly accounted for.
|
||||
* Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
1. Double-check that the extension `package.json` has the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
|
||||
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
|
||||
1. Create a PR for this release:
|
||||
* This PR will contain any missing bits from steps 1 and 2. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
|
||||
* Create a new branch for the release named after the new version. For example: `v1.3.6`
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.4.2 - 2 February 2021
|
||||
|
||||
- Add a status bar item for the CodeQL CLI to show the current version. [#741](https://github.com/github/vscode-codeql/pull/741)
|
||||
- Fix version constraint for flagging CLI support of non-destructive updates. [#744](https://github.com/github/vscode-codeql/pull/744)
|
||||
- Add a _More Information_ button in the telemetry popup that opens [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md) in a browser tab. [#742](https://github.com/github/vscode-codeql/pull/742)
|
||||
|
||||
## 1.4.1 - 29 January 2021
|
||||
|
||||
- Reword the telemetry modal dialog box. [#738](https://github.com/github/vscode-codeql/pull/738)
|
||||
|
||||
## 1.4.0 - 29 January 2021
|
||||
|
||||
- Fix bug where databases are not reregistered when the query server restarts. [#734](https://github.com/github/vscode-codeql/pull/734)
|
||||
- Fix bug where upgrade requests were erroneously being marked as failed. [#734](https://github.com/github/vscode-codeql/pull/734)
|
||||
- On a strictly opt-in basis, collect anonymized usage data from the VS Code extension, helping improve CodeQL's usability and performance. See [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/TELEMETRY.md) for more information on exactly what data is collected and what it is used for. [#611](https://github.com/github/vscode-codeql/pull/611)
|
||||
- On a strictly opt-in basis, collect anonymized usage data from the VS Code extension, helping improve CodeQL's usability and performance. See [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md) for more information on exactly what data is collected and what it is used for. [#611](https://github.com/github/vscode-codeql/pull/611)
|
||||
|
||||
## 1.3.10 - 20 January 2021
|
||||
|
||||
|
||||
BIN
extensions/ql-vscode/media/canary-logo.png
Normal file
BIN
extensions/ql-vscode/media/canary-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
2
extensions/ql-vscode/package-lock.json
generated
2
extensions/ql-vscode/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.3.11",
|
||||
"version": "1.4.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.2",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -179,8 +179,7 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "application",
|
||||
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/TELEMETRY.md)",
|
||||
"description": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub."
|
||||
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md)"
|
||||
},
|
||||
"codeQL.telemetry.logTelemetry": {
|
||||
"type": "boolean",
|
||||
@@ -207,6 +206,10 @@
|
||||
"command": "codeQL.quickQuery",
|
||||
"title": "CodeQL: Quick Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openDocumentation",
|
||||
"title": "CodeQL: Open Documentation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseFolder",
|
||||
"title": "Choose Database from Folder",
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
withProgress,
|
||||
ProgressUpdate
|
||||
} from './commandRunner';
|
||||
import { CodeQlStatusBarHandler } from './status-bar';
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -290,14 +291,22 @@ export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionIn
|
||||
return result;
|
||||
}
|
||||
|
||||
async function installOrUpdateThenTryActivate(config: DistributionUpdateConfig): Promise<CodeQLExtensionInterface | {}> {
|
||||
async function installOrUpdateThenTryActivate(
|
||||
config: DistributionUpdateConfig
|
||||
): Promise<CodeQLExtensionInterface | {}> {
|
||||
|
||||
await installOrUpdateDistribution(config);
|
||||
|
||||
// Display the warnings even if the extension has already activated.
|
||||
const distributionResult = await getDistributionDisplayingDistributionWarnings();
|
||||
let extensionInterface: CodeQLExtensionInterface | {} = {};
|
||||
if (!beganMainExtensionActivation && distributionResult.kind !== FindDistributionResultKind.NoDistribution) {
|
||||
extensionInterface = await activateWithInstalledDistribution(ctx, distributionManager);
|
||||
extensionInterface = await activateWithInstalledDistribution(
|
||||
ctx,
|
||||
distributionManager,
|
||||
distributionConfigListener
|
||||
);
|
||||
|
||||
} else if (distributionResult.kind === FindDistributionResultKind.NoDistribution) {
|
||||
registerErrorStubs([checkForUpdatesCommand], command => async () => {
|
||||
const installActionName = 'Install CodeQL CLI';
|
||||
@@ -339,7 +348,8 @@ export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionIn
|
||||
|
||||
async function activateWithInstalledDistribution(
|
||||
ctx: ExtensionContext,
|
||||
distributionManager: DistributionManager
|
||||
distributionManager: DistributionManager,
|
||||
distributionConfigListener: DistributionConfigListener
|
||||
): Promise<CodeQLExtensionInterface> {
|
||||
beganMainExtensionActivation = true;
|
||||
// Remove any error stubs command handlers left over from first part
|
||||
@@ -360,6 +370,9 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
ctx.subscriptions.push(cliServer);
|
||||
|
||||
const statusBar = new CodeQlStatusBarHandler(cliServer, distributionConfigListener);
|
||||
ctx.subscriptions.push(statusBar);
|
||||
|
||||
logger.log('Initializing query server client.');
|
||||
const qs = new qsClient.QueryServerClient(
|
||||
qlConfigurationListener,
|
||||
@@ -659,6 +672,10 @@ async function activateWithInstalledDistribution(
|
||||
})
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.openDocumentation', async () =>
|
||||
env.openExternal(Uri.parse('https://codeql.github.com/docs/'))));
|
||||
|
||||
logger.log('Starting language server.');
|
||||
ctx.subscriptions.push(client.start());
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ import * as yaml from 'js-yaml';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
ExtensionContext,
|
||||
Uri,
|
||||
window as Window,
|
||||
workspace
|
||||
workspace,
|
||||
env
|
||||
} from 'vscode';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { logger } from './logging';
|
||||
@@ -100,6 +102,41 @@ export async function showBinaryChoiceDialog(message: string, modal = true): Pro
|
||||
return chosenItem?.title === yesItem.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a modal dialog for the user to make a yes/no choice.
|
||||
*
|
||||
* @param message The message to show.
|
||||
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
|
||||
* be closed even if the user does not make a choice.
|
||||
*
|
||||
* @return
|
||||
* `true` if the user clicks 'Yes',
|
||||
* `false` if the user clicks 'No' or cancels the dialog,
|
||||
* `undefined` if the dialog is closed without the user making a choice.
|
||||
*/
|
||||
export async function showBinaryChoiceWithUrlDialog(message: string, url: string): Promise<boolean | undefined> {
|
||||
const urlItem = { title: 'More Information', isCloseAffordance: false };
|
||||
const yesItem = { title: 'Yes', isCloseAffordance: false };
|
||||
const noItem = { title: 'No', isCloseAffordance: true };
|
||||
let chosenItem;
|
||||
|
||||
// Keep the dialog open as long as the user is clicking the 'more information' option.
|
||||
// To prevent an infinite loop, if the user clicks 'more information' 5 times, close the dialog and return cancelled
|
||||
let count = 0;
|
||||
do {
|
||||
chosenItem = await Window.showInformationMessage(message, { modal: true }, urlItem, yesItem, noItem);
|
||||
if (chosenItem === urlItem) {
|
||||
await env.openExternal(Uri.parse(url, true));
|
||||
}
|
||||
count++;
|
||||
} while (chosenItem === urlItem && count < 5);
|
||||
|
||||
if (!chosenItem || chosenItem.title === urlItem.title) {
|
||||
return undefined;
|
||||
}
|
||||
return chosenItem.title === yesItem.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an information message with a customisable action.
|
||||
* @param message The message to show.
|
||||
|
||||
42
extensions/ql-vscode/src/status-bar.ts
Normal file
42
extensions/ql-vscode/src/status-bar.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ConfigurationChangeEvent, StatusBarAlignment, StatusBarItem, window, workspace } from 'vscode';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { CANARY_FEATURES, DistributionConfigListener } from './config';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
|
||||
/**
|
||||
* Creates and manages a status bar item for codeql. THis item contains
|
||||
* the current codeQL cli version as well as a notification if you are
|
||||
* in canary mode
|
||||
*
|
||||
*/
|
||||
export class CodeQlStatusBarHandler extends DisposableObject {
|
||||
|
||||
private readonly item: StatusBarItem;
|
||||
|
||||
constructor(private cli: CodeQLCliServer, distributionConfigListener: DistributionConfigListener) {
|
||||
super();
|
||||
this.item = window.createStatusBarItem(StatusBarAlignment.Right);
|
||||
this.push(this.item);
|
||||
this.push(workspace.onDidChangeConfiguration(this.handleDidChangeConfiguration, this));
|
||||
this.push(distributionConfigListener.onDidChangeConfiguration(() => this.updateStatusItem()));
|
||||
this.item.command = 'codeQL.openDocumentation';
|
||||
this.updateStatusItem();
|
||||
}
|
||||
|
||||
private handleDidChangeConfiguration(e: ConfigurationChangeEvent) {
|
||||
if (e.affectsConfiguration(CANARY_FEATURES.qualifiedName)) {
|
||||
this.updateStatusItem();
|
||||
}
|
||||
}
|
||||
|
||||
private async updateStatusItem() {
|
||||
const canary = CANARY_FEATURES.getValue() ? ' (Canary)' : '';
|
||||
// since getting the verison may take a few seconds, initialize with some
|
||||
// meaningful text.
|
||||
this.item.text = `CodeQL${canary}`;
|
||||
|
||||
const version = await this.cli.getVersion();
|
||||
this.item.text = `CodeQL CLI v${version}${canary}`;
|
||||
this.item.show();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { ConfigListener, CANARY_FEATURES, ENABLE_TELEMETRY, GLOBAL_ENABLE_TELEME
|
||||
import * as appInsights from 'applicationinsights';
|
||||
import { logger } from './logging';
|
||||
import { UserCancellationException } from './commandRunner';
|
||||
import { showBinaryChoiceDialog } from './helpers';
|
||||
import { showBinaryChoiceWithUrlDialog } from './helpers';
|
||||
|
||||
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
|
||||
const key = 'REPLACE-APP-INSIGHTS-KEY';
|
||||
@@ -164,13 +164,9 @@ export class TelemetryListener extends ConfigListener {
|
||||
let result = undefined;
|
||||
if (GLOBAL_ENABLE_TELEMETRY.getValue()) {
|
||||
// Extension won't start until this completes.
|
||||
result = await showBinaryChoiceDialog(
|
||||
'Do we have your permission to collect usage data and metrics to help us improve CodeQL for VSCode? See [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/TELEMETRY.md) for details of what we collect and how we use it.',
|
||||
// We make this dialog modal for now.
|
||||
// If we do decide to keep this dialog as modal, then this implementation can change and
|
||||
// we no longer need to call Promise.race. Before committing this PR, we need to make
|
||||
// this decision.
|
||||
true
|
||||
result = await showBinaryChoiceWithUrlDialog(
|
||||
'Does the CodeQL Extension by GitHub have your permission to collect usage data and metrics to help us improve CodeQL for VSCode?',
|
||||
'https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md'
|
||||
);
|
||||
}
|
||||
if (result !== undefined) {
|
||||
|
||||
@@ -25,7 +25,7 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
|
||||
* resolving upgrades. We check for a version of codeql that has all three features.
|
||||
*/
|
||||
export async function hasNondestructiveUpgradeCapabilities(qs: qsClient.QueryServerClient): Promise<boolean> {
|
||||
return semver.gte(await qs.cliServer.getVersion(), '2.4.1');
|
||||
return semver.gte(await qs.cliServer.getVersion(), '2.4.2');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { ExtensionContext, Memento } from 'vscode';
|
||||
import { ExtensionContext, Memento, window } from 'vscode';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as tmp from 'tmp';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { getInitialQueryContents, InvocationRateLimiter, isLikelyDbLanguageFolder } from '../../helpers';
|
||||
import { getInitialQueryContents, InvocationRateLimiter, isLikelyDbLanguageFolder, showBinaryChoiceDialog, showBinaryChoiceWithUrlDialog, showInformationMessageWithAction } from '../../helpers';
|
||||
import { reportStreamProgress } from '../../commandRunner';
|
||||
import Sinon = require('sinon');
|
||||
import { fail } from 'assert';
|
||||
|
||||
describe('helpers', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
@@ -225,4 +227,91 @@ describe('helpers', () => {
|
||||
message: 'My prefix (Size unknown)',
|
||||
});
|
||||
});
|
||||
|
||||
describe('open dialog', () => {
|
||||
let showInformationMessageSpy: Sinon.SinonStub;
|
||||
beforeEach(() => {
|
||||
showInformationMessageSpy = sandbox.stub(window, 'showInformationMessage');
|
||||
});
|
||||
|
||||
it('should show a binary choice dialog and return `yes`', (done) => {
|
||||
// pretend user chooses 'yes'
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(2);
|
||||
const res = showBinaryChoiceDialog('xxx');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(true);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show a binary choice dialog and return `no`', (done) => {
|
||||
// pretend user chooses 'no'
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(3);
|
||||
const res = showBinaryChoiceDialog('xxx');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(false);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show an info dialog and confirm the action', (done) => {
|
||||
// pretend user chooses to run action
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(1);
|
||||
const res = showInformationMessageWithAction('xxx', 'yyy');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(true);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show an action dialog and avoid choosing the action', (done) => {
|
||||
// pretend user does not choose to run action
|
||||
showInformationMessageSpy.onCall(0).resolves(undefined);
|
||||
const res = showInformationMessageWithAction('xxx', 'yyy');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(false);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show a binary choice dialog with a url and return `yes`', (done) => {
|
||||
// pretend user clicks on the url twice and then clicks 'yes'
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(1).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(2).resolvesArg(3);
|
||||
const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(true);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show a binary choice dialog with a url and return `no`', (done) => {
|
||||
// pretend user clicks on the url twice and then clicks 'no'
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(1).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(2).resolvesArg(4);
|
||||
const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');
|
||||
res.then((val) => {
|
||||
expect(val).to.eq(false);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
|
||||
it('should show a binary choice dialog and exit after clcking `more info` 5 times', (done) => {
|
||||
// pretend user clicks on the url twice and then clicks 'no'
|
||||
showInformationMessageSpy.onCall(0).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(1).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(2).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(3).resolvesArg(2);
|
||||
showInformationMessageSpy.onCall(4).resolvesArg(2);
|
||||
const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');
|
||||
res.then((val) => {
|
||||
// No choie was made
|
||||
expect(val).to.eq(undefined);
|
||||
expect(showInformationMessageSpy.getCalls().length).to.eq(5);
|
||||
done();
|
||||
}).catch(e => fail(e));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,7 +249,7 @@ describe('telemetry reporting', function() {
|
||||
});
|
||||
|
||||
it('should request permission if popup has never been seen before', async () => {
|
||||
sandbox.stub(window, 'showInformationMessage').resolvesArg(2 /* "yes" item */);
|
||||
sandbox.stub(window, 'showInformationMessage').resolvesArg(3 /* "yes" item */);
|
||||
await ctx.globalState.update('telemetry-request-viewed', false);
|
||||
await enableTelemetry('codeQL.telemetry', false);
|
||||
|
||||
@@ -262,7 +262,7 @@ describe('telemetry reporting', function() {
|
||||
});
|
||||
|
||||
it('should prevent telemetry if permission is denied', async () => {
|
||||
sandbox.stub(window, 'showInformationMessage').resolvesArg(3 /* "no" item */);
|
||||
sandbox.stub(window, 'showInformationMessage').resolvesArg(4 /* "no" item */);
|
||||
await ctx.globalState.update('telemetry-request-viewed', false);
|
||||
await enableTelemetry('codeQL.telemetry', true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user