Compare commits

...

8 Commits

Author SHA1 Message Date
Andrew Eisenberg
27529bfc33 v1.4.2
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
2021-02-02 14:23:49 -08:00
Andrew Eisenberg
0e4ae83e74 ` 2021-02-02 12:38:53 -08:00
Andrew Eisenberg
3b1ff0f4a3 Add a codeql status bar item
Includes the current cli version as well as the
canary status (codeQL.canary) in the settings.
2021-02-02 09:40:59 -08:00
Andrew Eisenberg
5079abd06f Fix version constraint
Non-destructive upgrades only exist in versions >= 2.4.2
2021-02-02 09:17:33 -08:00
aeisenberg
4e94f70e6f Bump version to v1.4.2 2021-01-29 21:45:42 -08:00
Andrew Eisenberg
79e2666586 v1.4.1
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
2021-01-29 21:37:29 -08:00
Andrew Eisenberg
02080cd797 Change text and fix link of modal dialog
Modal dialogs do not allow for markdown text. The link was invalid.
Also, make CodeQL more prominent in the dialog.
2021-01-29 17:46:42 -08:00
aeisenberg
7347ff5512 Bump version to v1.4.1 2021-01-29 16:07:07 -08:00
12 changed files with 217 additions and 23 deletions

View File

@@ -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`

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "vscode-codeql",
"version": "1.3.11",
"version": "1.4.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -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",

View File

@@ -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());

View File

@@ -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.

View 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();
}
}

View File

@@ -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) {

View File

@@ -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');
}

View File

@@ -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));
});
});
});

View File

@@ -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);