diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd602655..a0ca95d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,9 @@ # Snyk Security - Code and Open Source Dependencies Changelog ## [1.23.2] - ### Fixed - - Removed false positives feature flag +- View management: show accurate information during startup of the plugin ## [1.23.1] diff --git a/package.json b/package.json index dc412c457..6cd2dd0ce 100644 --- a/package.json +++ b/package.json @@ -230,41 +230,35 @@ }, "views": { "snyk": [ - { - "id": "snyk.views.features", - "name": "Snyk", - "when": "snyk:loggedIn && !snyk:featuresSelected && !snyk:error", - "type": "webview" - }, { "id": "snyk.views.welcome", "name": "Snyk", - "when": "!snyk:loggedIn || snyk:error || snyk:featuresSelected && !snyk:workspaceFound" + "when": "!snyk:loggedIn || snyk:error || !snyk:workspaceFound" }, { "id": "snyk.views.analysis.oss", "name": "Open Source Security", - "when": "snyk:loggedIn && snyk:featuresSelected && snyk:workspaceFound && !snyk:error" + "when": "snyk:initialized && snyk:loggedIn && snyk:workspaceFound && !snyk:error" }, { "id": "snyk.views.analysis.code.security", "name": "Code Security", - "when": "snyk:lsCodePreview && snyk:loggedIn && snyk:codeEnabled && snyk:featuresSelected && snyk:workspaceFound && !snyk:error" + "when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error" }, { "id": "snyk.views.analysis.configuration", - "name": "Configuration", - "when": "snyk:loggedIn && snyk:featuresSelected && snyk:workspaceFound && !snyk:error" + "name": "Configuration Issues", + "when": "snyk:initialized && snyk:loggedIn && snyk:workspaceFound && !snyk:error" }, { "id": "snyk.views.analysis.code.quality", "name": "Code Quality", - "when": "snyk:lsCodePreview && snyk:loggedIn && snyk:codeEnabled && snyk:featuresSelected && snyk:workspaceFound && !snyk:error" + "when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error" }, { "id": "snyk.views.analysis.code.enablement", "name": "Code Security & Quality", - "when": "snyk:loggedIn && snyk:featuresSelected && !snyk:codeEnabled && snyk:workspaceFound && !snyk:error" + "when": "snyk:initialized && snyk:loggedIn && !snyk:codeEnabled && snyk:workspaceFound && !snyk:error" }, { "id": "snyk.views.support", @@ -278,15 +272,20 @@ "contents": "Snyk has encountered a problem. Please restart the extension: \n[Restart](command:snyk.start 'Restart Snyk')\nIf the error persists, please check your [settings](command:snyk.settings) and [contact us](https://snyk.io/contact-us/?utm_source=vsc)!", "when": "snyk:error == 'blocking'" }, + { + "view": "snyk.views.welcome", + "contents": "Welcome to Snyk for Visual Studio Code. šŸ‘‹\nšŸ‘‰ Please wait, the extension is loading...", + "when": "!snyk:error && !snyk:initialized" + }, { "view": "snyk.views.welcome", "contents": "Welcome to Snyk for Visual Studio Code. šŸ‘‹\nšŸ‘‰ Connect with Snyk to start your first analysis!\nWhen scanning folder files, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan projects you trust. [More info](https://docs.snyk.io/ide-tools/visual-studio-code-extension/workspace-trust)\n[Trust workspace and connect](command:snyk.initiateLogin 'Connect with Snyk')\nBy connecting your account with Snyk, you agree to the Snyk [Privacy Policy](https://snyk.io/policies/privacy), and the Snyk [Terms of Service](https://snyk.io/policies/terms-of-service).", - "when": "!snyk:error && !snyk:loggedIn" + "when": "!snyk:error && snyk:initialized && !snyk:loggedIn" }, { "view": "snyk.views.welcome", "contents": "We are now redirecting you to our auth page, go ahead and log in. If a browser window doesn't open after a few seconds, please copy the url below and manually paste it in a browser.\n[Copy URL to clipboard](command:snyk.copyAuthLink 'Copy URL to clipboard')", - "when": "!snyk:error && !snyk:loggedIn && snyk:authenticating" + "when": "!snyk:error && snyk:initialized && !snyk:loggedIn && snyk:authenticating" }, { "view": "snyk.views.analysis.code.enablement", @@ -295,7 +294,7 @@ { "view": "snyk.views.welcome", "contents": "Open a workspace or a folder in Visual Studio Code to start the analysis.", - "when": "!snyk:error && snyk:loggedIn && snyk:featuresSelected && !snyk:workspaceFound" + "when": "!snyk:error && snyk:initialized && snyk:loggedIn && !snyk:workspaceFound" } ], "menus": { @@ -453,6 +452,6 @@ "uuid": "^8.3.2", "validate-npm-package-name": "^3.0.0", "vscode-languageclient": "8.1.0", - "vscode-languageserver-textdocument":"^1.0.8" + "vscode-languageserver-textdocument": "^1.0.8" } } diff --git a/src/snyk/base/modules/snykLib.ts b/src/snyk/base/modules/snykLib.ts index e96ef1ee1..f9a639f03 100644 --- a/src/snyk/base/modules/snykLib.ts +++ b/src/snyk/base/modules/snykLib.ts @@ -30,17 +30,12 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { await this.contextService.setContext(SNYK_CONTEXT.LOGGEDIN, true); if (!configuration.getFeaturesConfiguration()) { - await this.contextService.setContext(SNYK_CONTEXT.FEATURES_SELECTED, false); return; } - await this.contextService.setContext(SNYK_CONTEXT.FEATURES_SELECTED, true); - - const workspacePaths = vsCodeWorkspace.getWorkspaceFolders(); - await this.setWorkspaceContext(workspacePaths); - await this.user.identify(vsCodeCommands, this.analytics); + const workspacePaths = vsCodeWorkspace.getWorkspaceFolders(); if (workspacePaths.length) { this.logFullAnalysisIsTriggered(manual); void this.startOssAnalysis(manual, false); @@ -80,7 +75,7 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { await this.contextService.setContext(SNYK_CONTEXT.ADVANCED, configuration.shouldShowAdvancedView); } - private async setWorkspaceContext(workspacePaths: string[]): Promise { + protected async setWorkspaceContext(workspacePaths: string[]): Promise { const workspaceFound = !!workspacePaths.length; await this.contextService.setContext(SNYK_CONTEXT.WORKSPACE_FOUND, workspaceFound); } diff --git a/src/snyk/base/views/featureSelection/featuresViewProvider.ts b/src/snyk/base/views/featureSelection/featuresViewProvider.ts deleted file mode 100644 index fb431f4c0..000000000 --- a/src/snyk/base/views/featureSelection/featuresViewProvider.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import * as vscode from 'vscode'; -import { FeaturesConfiguration } from '../../../common/configuration/configuration'; -import { configuration } from '../../../common/configuration/instance'; -import { SNYK_CONTEXT } from '../../../common/constants/views'; -import { IContextService } from '../../../common/services/contextService'; -import { getNonce } from '../../../common/views/nonce'; - -enum FeaturesViewEventMessageType { - FeaturesSelected = 'featuresSelected', -} - -type FeaturesViewEventMessage = { - type: FeaturesViewEventMessageType; - value: unknown; -}; - -export class FeaturesViewProvider implements vscode.WebviewViewProvider { - private view?: vscode.WebviewView; - - constructor(private readonly _extensionUri: vscode.Uri, private readonly contextService: IContextService) {} - - public resolveWebviewView( - webviewView: vscode.WebviewView, - _context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken, - ): void { - this.view = webviewView; - - webviewView.webview.options = { - // Allow scripts in the webview - enableScripts: true, - localResourceRoots: [this._extensionUri], - }; - - webviewView.webview.html = this.getHtmlForWebview(webviewView.webview); - - webviewView.webview.onDidReceiveMessage(async (data: FeaturesViewEventMessage) => { - switch (data.type) { - case FeaturesViewEventMessageType.FeaturesSelected: { - await configuration.setFeaturesConfiguration(data.value as FeaturesConfiguration); - await this.contextService.setContext(SNYK_CONTEXT.FEATURES_SELECTED, true); - break; - } - } - }); - } - - getWebView(): vscode.WebviewView | undefined { - return this.view; - } - - private getHtmlForWebview(webview: vscode.Webview) { - const scriptUri = this.getWebViewUri('out', 'snyk', 'base', 'views', 'featureSelection', 'featuresViewScript.js'); - const styleVSCodeUri = this.getWebViewUri('media', 'views', 'common', 'vscode.css'); - const styleUri = this.getWebViewUri('media', 'views', 'featureSelection', 'featureSelection.css'); - const avatarUri = this.getWebViewUri('media', 'images', 'avatar-transparent.svg'); - - // Use a nonce to only allow a specific script to be run. - const nonce = getNonce(); - - return ` - - - - - - - - - - - - - -
- -

Let's start analyzing your code

-
- -
- - -
-
- - -
- - - - - - `; - } - - private getWebViewUri(...pathSegments: string[]) { - return this.view?.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, ...pathSegments)); - } -} diff --git a/src/snyk/base/views/featureSelection/featuresViewScript.ts b/src/snyk/base/views/featureSelection/featuresViewScript.ts deleted file mode 100644 index d77a4fc25..000000000 --- a/src/snyk/base/views/featureSelection/featuresViewScript.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/// - -declare const acquireVsCodeApi: any; - -// This script will be run within the webview itself -// It cannot access the main VS Code APIs directly. -(function () { - const vscode = acquireVsCodeApi(); - - document.querySelector('.analyze-button')?.addEventListener('click', () => { - saveProductSelection(); - }); - - function saveProductSelection() { - vscode.postMessage({ - type: 'featuresSelected', - value: { - codeSecurityEnabled: (document.getElementById('codeSecurityEnabled') as HTMLInputElement)?.checked, - codeQualityEnabled: (document.getElementById('codeQualityEnabled') as HTMLInputElement)?.checked, - }, - }); - } -})(); diff --git a/src/snyk/common/constants/views.ts b/src/snyk/common/constants/views.ts index b993f3763..6616a1ef5 100644 --- a/src/snyk/common/constants/views.ts +++ b/src/snyk/common/constants/views.ts @@ -13,12 +13,11 @@ export const SNYK_VIEW_ANALYSIS_IAC = 'snyk.views.analysis.configuration'; // Having multiple boolean contexts instead of a single context // with multiple values helps us to avoid flickering UI. export const SNYK_CONTEXT = { + INITIALIZED: 'initialized', // default to loading state (notLoading = false when boolean is initialized) LOGGEDIN: 'loggedIn', AUTHENTICATING: 'authenticating', - FEATURES_SELECTED: 'featuresSelected', CODE_ENABLED: 'codeEnabled', CODE_LOCAL_ENGINE_ENABLED: 'codeLocalEngineEnabled', - LS_CODE_PREVIEW: 'lsCodePreview', WORKSPACE_FOUND: 'workspaceFound', ERROR: 'error', MODE: 'mode', diff --git a/src/snyk/common/services/contextService.ts b/src/snyk/common/services/contextService.ts index 3e6208408..c0c5ef901 100644 --- a/src/snyk/common/services/contextService.ts +++ b/src/snyk/common/services/contextService.ts @@ -7,7 +7,6 @@ export interface IContextService { shouldShowCodeAnalysis: boolean; shouldShowOssAnalysis: boolean; shouldShowIacAnalysis: boolean; - isCodeInLsPreview: boolean; setContext(key: string, value: unknown): Promise; } @@ -25,10 +24,6 @@ export class ContextService implements IContextService { await setContext(key, value); } - get isCodeInLsPreview(): boolean { - return !!this.viewContext[SNYK_CONTEXT.LS_CODE_PREVIEW]; - } - get shouldShowCodeAnalysis(): boolean { return this.shouldShowAnalysis && !!this.viewContext[SNYK_CONTEXT.CODE_ENABLED]; } diff --git a/src/snyk/common/services/viewManagerService.ts b/src/snyk/common/services/viewManagerService.ts index 58cffac6e..c1706b149 100644 --- a/src/snyk/common/services/viewManagerService.ts +++ b/src/snyk/common/services/viewManagerService.ts @@ -1,12 +1,11 @@ import _ from 'lodash'; import { EventEmitter, TreeView } from 'vscode'; -import { FeaturesViewProvider } from '../../base/views/featureSelection/featuresViewProvider'; import { FeaturesConfiguration } from '../configuration/configuration'; import { configuration } from '../configuration/instance'; import { REFRESH_VIEW_DEBOUNCE_INTERVAL } from '../constants/general'; import { TreeNode } from '../views/treeNode'; -export type ViewType = FeaturesViewProvider | TreeView; +export type ViewType = TreeView; export class ViewContainer { private container = new Map(); diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index aeae6683a..01b31af4a 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -6,7 +6,6 @@ import SnykLib from './base/modules/snykLib'; import { AuthenticationService } from './base/services/authenticationService'; import { ScanModeService } from './base/services/scanModeService'; import { EmptyTreeDataProvider } from './base/views/emptyTreeDataProvider'; -import { FeaturesViewProvider } from './base/views/featureSelection/featuresViewProvider'; import { SupportProvider } from './base/views/supportProvider'; import { messages } from './cli/messages/messages'; import { Iteratively } from './common/analytics/itly'; @@ -37,7 +36,6 @@ import { SNYK_VIEW_ANALYSIS_CODE_SECURITY, SNYK_VIEW_ANALYSIS_IAC, SNYK_VIEW_ANALYSIS_OSS, - SNYK_VIEW_FEATURES, SNYK_VIEW_SUPPORT, SNYK_VIEW_WELCOME, } from './common/constants/views'; @@ -109,6 +107,19 @@ class SnykExtension extends SnykLib implements IExtension { } private async initializeExtension(vscodeContext: vscode.ExtensionContext, snykConfiguration?: SnykConfiguration) { + // initialize context correctly + // see package.json when each view is shown, based on context value + await this.contextService.setContext(SNYK_CONTEXT.INITIALIZED, false); + + // default to true, as the check is async and can only be done after startup of LS + // if set to true, the option to enable code is not shown in the initialization phase + await this.contextService.setContext(SNYK_CONTEXT.CODE_ENABLED, true); + + // set the workspace context so that the text to add folders is only shown if really the case + // initializing after LS startup and just before scan is too late + const workspacePaths = vsCodeWorkspace.getWorkspaceFolders(); + await this.setWorkspaceContext(workspacePaths); + this.user = await User.getAnonymous(this.context, Logger); this.analytics = new Iteratively( @@ -287,10 +298,7 @@ class SnykExtension extends SnykLib implements IExtension { configuration, ); - const featuresViewProvider = new FeaturesViewProvider(vscodeContext.extensionUri, this.contextService); - vscodeContext.subscriptions.push( - vscode.window.registerWebviewViewProvider(SNYK_VIEW_FEATURES, featuresViewProvider), vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_OSS, ossVulnerabilityProvider), vscode.window.registerTreeDataProvider(SNYK_VIEW_SUPPORT, new SupportProvider()), ); @@ -332,7 +340,6 @@ class SnykExtension extends SnykLib implements IExtension { // Fill the view container to expose views for tests const viewContainer = this.viewManagerService.viewContainer; viewContainer.set(SNYK_VIEW_WELCOME, welcomeTree); - viewContainer.set(SNYK_VIEW_FEATURES, featuresViewProvider); vscode.workspace.onDidChangeWorkspaceFolders(e => { this.workspaceTrust.resetTrustedFoldersCache(); @@ -393,14 +400,19 @@ class SnykExtension extends SnykLib implements IExtension { configuration, ); - await this.contextService.setContext(SNYK_CONTEXT.LS_CODE_PREVIEW, true); - // noinspection ES6MissingAwait void this.advisorScoreDisposable.activate(); // Wait for LS startup to finish before updating the codeEnabled context // The codeEnabled context depends on an LS command await this.languageServer.start(); + + // initialize contexts + const loggedIn = await configuration.getToken(); + if (loggedIn != undefined) { + await this.contextService.setContext(SNYK_CONTEXT.LOGGEDIN, true); + } + await this.contextService.setContext(SNYK_CONTEXT.INITIALIZED, true); await this.codeSettings.updateIsCodeEnabled(); // Actually start analysis diff --git a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewScript.ts b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewScript.ts index e79ce3182..b64485203 100644 --- a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewScript.ts +++ b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewScript.ts @@ -6,6 +6,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /// +// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-explicit-any +declare const acquireVsCodeApi: any; + // This script will be run within the webview itself // It cannot access the main VS Code APIs directly. (function () { @@ -101,7 +104,7 @@ rule: suggestion.rule, id: suggestion.id, severity: suggestion.severity, - lineOnly: !!lineOnly, + lineOnly: lineOnly, }, }); } diff --git a/src/test/integration/viewNavigation.test.ts b/src/test/integration/viewNavigation.test.ts deleted file mode 100644 index d22d6aad9..000000000 --- a/src/test/integration/viewNavigation.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { strictEqual } from 'assert'; -import { getExtension } from '../../extension'; -import { SNYK_VIEW_FEATURES, SNYK_VIEW_WELCOME } from '../../snyk/common/constants/views'; -import { TreeView } from 'vscode'; -import { FeaturesViewProvider } from '../../snyk/base/views/featureSelection/featuresViewProvider'; -import { TreeNode } from '../../snyk/common/views/treeNode'; -import { configuration } from '../../snyk/common/configuration/instance'; - -suite('View Navigation', () => { - setup(async () => { - await configuration.clearToken(); - await configuration.setFeaturesConfiguration(undefined); - }); - - teardown(async () => { - await configuration.clearToken(); - }); - - test('"Feature view is seen after user authenticates within welcome view', async () => { - const extension = getExtension(); - const viewContainer = extension.viewManagerService.viewContainer; - const welcomeTree = viewContainer.get>(SNYK_VIEW_WELCOME); - - // 1. Check welcome view is visible - strictEqual(welcomeTree?.visible, true); - - // 2. Authenticate a user - await configuration.setToken('fake-token'); - - // Give time to pick up the setting change - const sleep = (duration: number) => new Promise(resolve => setTimeout(resolve, duration)); - await sleep(1500); - - // 3. Assert - const featuresView = viewContainer.get(SNYK_VIEW_FEATURES); - strictEqual(featuresView?.getWebView()?.visible, true); - }); -}); diff --git a/src/test/unit/snykCode/codeSettings.test.ts b/src/test/unit/snykCode/codeSettings.test.ts index 8d47b96e5..604e1e5b3 100644 --- a/src/test/unit/snykCode/codeSettings.test.ts +++ b/src/test/unit/snykCode/codeSettings.test.ts @@ -20,7 +20,6 @@ suite('Snyk Code Settings', () => { shouldShowCodeAnalysis: false, shouldShowOssAnalysis: false, shouldShowIacAnalysis: false, - isCodeInLsPreview: false, viewContext: {}, };