src/goEnvironmentStatus.ts: notify user of available Go updates

Get the latest go versions from golang.org/dl and notify the user if a
newer version than the one they are currently usingis available. The
latest versions are cached for 24 hours.

Notifications are turned on and off using the
go.useGoProxyToCheckForToolUpdates setting.

'Don't show again' will keep users from being shown notifications for
the specified versions.

Updates golang/vscode-go#483

Change-Id: I41033cbf995f76be5c08c3ed9f4d1b81a4fd2aca
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/248184
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Suzy Mueller 2020-08-18 12:04:46 -04:00
Родитель 1e4dbe2d65
Коммит 7f493bf283
6 изменённых файлов: 251 добавлений и 6 удалений

Просмотреть файл

@ -227,7 +227,7 @@ Complete functions with their parameter signature, excluding the variable types
### `go.useGoProxyToCheckForToolUpdates`
When enabled, the extension automatically checks the Go proxy if there are updates available for the Go tools (at present, only gopls) it depends on and prompts the user accordingly
When enabled, the extension automatically checks the Go proxy if there are updates available for Go and the Go tools (at present, only gopls) it depends on and prompts the user accordingly
### `go.useLanguageServer`

Просмотреть файл

@ -1560,7 +1560,7 @@
"go.useGoProxyToCheckForToolUpdates": {
"type": "boolean",
"default": true,
"description": "When enabled, the extension automatically checks the Go proxy if there are updates available for the Go tools (at present, only gopls) it depends on and prompts the user accordingly"
"description": "When enabled, the extension automatically checks the Go proxy if there are updates available for Go and the Go tools (at present, only gopls) it depends on and prompts the user accordingly"
},
"go.gotoSymbol.includeImports": {
"type": "boolean",

Просмотреть файл

@ -7,15 +7,16 @@
import cp = require('child_process');
import fs = require('fs');
import moment = require('moment');
import os = require('os');
import path = require('path');
import { promisify } from 'util';
import vscode = require('vscode');
import WebRequest = require('web-request');
import { toolInstallationEnvironment } from './goEnv';
import { outputChannel } from './goStatus';
import { getFromWorkspaceState, updateWorkspaceState } from './stateUtils';
import { getBinPath, getGoVersion, getTempFilePath, GoVersion, rmdirRecursive } from './util';
import { hideGoStatus, outputChannel, showGoStatus } from './goStatus';
import { getFromGlobalState, getFromWorkspaceState, updateGlobalState, updateWorkspaceState } from './stateUtils';
import { getBinPath, getGoConfig, getGoVersion, getTempFilePath, GoVersion, rmdirRecursive } from './util';
import { correctBinname, getBinPathFromEnvVar, getCurrentGoRoot, pathExists } from './utils/goPath';
export class GoEnvironmentOption {
@ -451,3 +452,110 @@ async function fetchDownloadableGoVersions(): Promise<GoEnvironmentOption[]> {
return [...opts, new GoEnvironmentOption(dlPath, label)];
}, []);
}
export const latestGoVersionKey = 'latestGoVersions';
const oneday = 60 * 60 * 24 * 1000; // 24 hours in milliseconds
export async function getLatestGoVersions(): Promise<GoEnvironmentOption[]> {
const timeout = oneday;
const now = moment.now();
let results: GoEnvironmentOption[];
// Check if we can use cached results
const cachedResults = getFromGlobalState(latestGoVersionKey);
if (cachedResults && now - cachedResults.timestamp < timeout) {
results = cachedResults.goVersions;
} else {
// fetch the latest supported Go versions
try {
// fetch the latest Go versions and cache the results
results = await fetchDownloadableGoVersions();
await updateGlobalState(latestGoVersionKey, {
timestamp: now,
goVersions: results,
});
} catch (e) {
// hardcode the latest versions of Go in case golang.dl is unavailable
results = [
new GoEnvironmentOption('go get golang.org/dl/go1.15', 'Go 1.15'),
new GoEnvironmentOption('go get golang.org/dl/go1.14.7', 'Go 1.14.7'),
];
}
}
return results;
}
const dismissedGoVersionUpdatesKey = 'dismissedGoVersionUpdates';
export async function offerToInstallLatestGoVersion() {
const goConfig = getGoConfig();
if (!goConfig['useGoProxyToCheckForToolUpdates']) {
return;
}
let options = await getLatestGoVersions();
// filter out Go versions the user has already dismissed
let dismissedOptions: GoEnvironmentOption[];
dismissedOptions = await getFromGlobalState(dismissedGoVersionUpdatesKey);
if (!!dismissedOptions) {
options = options.filter((version) => !dismissedOptions.find((x) => x.label === version.label));
}
// compare to current go version.
const currentVersion = await getGoVersion();
if (!!currentVersion) {
options = options.filter((version) => currentVersion.lt(version.label));
}
// notify user that there is a newer version of Go available
if (options.length > 0) {
showGoStatus('Go Update Available', 'go.promptforgoinstall', 'A newer version of Go is available');
vscode.commands.registerCommand('go.promptforgoinstall', () => {
const download = {
title: 'Download',
async command() {
await vscode.env.openExternal(vscode.Uri.parse(`https://golang.org/dl/`));
}
};
const neverAgain = {
title: `Don't Show Again`,
async command() {
// mark these versions as seen
dismissedOptions = await getFromGlobalState(dismissedGoVersionUpdatesKey);
if (!dismissedOptions) {
dismissedOptions = [];
}
options.forEach((version) => {
dismissedOptions.push(version);
});
await updateGlobalState(dismissedGoVersionUpdatesKey, dismissedOptions);
}
};
let versionsText: string;
if (options.length > 1) {
versionsText = `${options.map((x) => x.label)
.reduce((prev, next) => {
return prev + ' and ' + next;
})} are available`;
} else {
versionsText = `${options[0].label} is available`;
}
vscode.window
.showInformationMessage(
`${versionsText}. You are currently using ${formatGoVersion(currentVersion)}.`,
download,
neverAgain
)
.then((selection) => {
hideGoStatus();
selection.command();
});
});
}
}

Просмотреть файл

@ -19,7 +19,7 @@ import {
import { GoDebugConfigurationProvider } from './goDebugConfiguration';
import { extractFunction, extractVariable } from './goDoctor';
import { toolExecutionEnvironment } from './goEnv';
import { chooseGoEnvironment, disposeGoStatusBar, setEnvironmentVariableCollection } from './goEnvironmentStatus';
import { chooseGoEnvironment, disposeGoStatusBar, offerToInstallLatestGoVersion, setEnvironmentVariableCollection } from './goEnvironmentStatus';
import { runFillStruct } from './goFillStruct';
import * as goGenerateTests from './goGenerateTests';
import { goGetPackage } from './goGetPackage';
@ -84,6 +84,7 @@ export function activate(ctx: vscode.ExtensionContext) {
updateGoVarsFromConfig().then(async () => {
suggestUpdates(ctx);
offerToInstallLatestGoVersion();
offerToInstallTools();
configureLanguageServer(ctx);

Просмотреть файл

@ -26,6 +26,10 @@ export function setGlobalState(state: vscode.Memento) {
globalState = state;
}
export function getGlobalState() {
return globalState;
}
export function getFromWorkspaceState(key: string, defaultValue?: any) {
if (!workspaceState) {
return defaultValue;

Просмотреть файл

@ -0,0 +1,132 @@
/*---------------------------------------------------------
* Copyright 2020 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import moment = require('moment');
import semver = require('semver');
import WebRequest = require('web-request');
import {
getLatestGoVersions,
GoEnvironmentOption,
latestGoVersionKey,
} from '../../src/goEnvironmentStatus';
import {
getGlobalState,
setGlobalState,
updateGlobalState,
} from '../../src/stateUtils';
import { MockMemento } from '../mocks/MockMemento';
describe('#getLatestGoVersion()', function () {
this.timeout(40000);
let sandbox: sinon.SinonSandbox | undefined;
let defaultMemento: vscode.Memento;
const webrequest = sinon.mock(WebRequest);
const mmnt = sinon.mock(moment);
const now = 100000000;
const oneday = 60 * 60 * 24 * 1000; // 24 hours in milliseconds
this.beforeAll(async () => {
defaultMemento = getGlobalState();
});
this.afterAll(async () => {
setGlobalState(defaultMemento);
});
this.beforeEach(() => {
sandbox = sinon.createSandbox();
setGlobalState(new MockMemento());
webrequest.expects('json')
.withArgs('https://golang.org/dl/?mode=json')
.returns([
{
version: 'go1.15.1',
stable: true,
},
{
version: 'go1.14.2',
stable: true,
},
]);
mmnt.expects('now')
.returns(now);
});
this.afterEach(async () => {
sandbox.restore();
});
it('should get latest go versions from golang.org/dl with empty cache', async () => {
const results = await getLatestGoVersions();
const want = [
{label: 'Go 1.15.1', binpath: 'go get golang.org/dl/go1.15.1'},
{label: 'Go 1.14.2', binpath: 'go get golang.org/dl/go1.14.2'},
];
assert(results.length === want.length);
for (let i = 0; i < results.length; i ++) {
assert(results[i].label === want[i].label);
assert(results[i].binpath === want[i].binpath);
}
});
const cacheVersions = [
new GoEnvironmentOption('go get golang.org/dl/go1.14.7', 'Go 1.14.7'),
new GoEnvironmentOption('go get golang.org/dl/go1.13.2', 'Go 1.13.2'),
];
it('should get latest go versions from golang.org/dl with timed out cache', async () => {
// add a timed out cache entry
await updateGlobalState(latestGoVersionKey, {
timestamp: now - (oneday + 1), // more than one day ago
goVersions: cacheVersions,
});
// run test
const results = await getLatestGoVersions();
const want = [
{label: 'Go 1.15.1', binpath: 'go get golang.org/dl/go1.15.1'},
{label: 'Go 1.14.2', binpath: 'go get golang.org/dl/go1.14.2'},
];
// check results
assert(results.length === want.length);
for (let i = 0; i < results.length; i ++) {
assert(results[i].label === want[i].label);
assert(results[i].binpath === want[i].binpath);
}
});
it('should get latest go versions from cache', async () => {
// add a valid cache entry
await updateGlobalState(latestGoVersionKey, {
timestamp: now - (oneday - 100), // less than one day ago
goVersions: cacheVersions,
});
// run test
const results = await getLatestGoVersions();
const want = [
{label: 'Go 1.14.7', binpath: 'go get golang.org/dl/go1.14.7'},
{label: 'Go 1.13.2', binpath: 'go get golang.org/dl/go1.13.2'},
];
// check results
assert(results.length === want.length);
for (let i = 0; i < results.length; i ++) {
assert(results[i].label === want[i].label);
assert(results[i].binpath === want[i].binpath);
}
});
});