diff --git a/.eslintrc.js b/.eslintrc.js
index 9cb163203..090414c59 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -52,5 +52,6 @@ module.exports = {
"no-unsafe-finally": "error",
"new-parens": "error",
"no-throw-literal": "error",
+ "no-useless-catch": "off"
}
}
\ No newline at end of file
diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml
index 01586c02d..ab9dceb52 100644
--- a/.github/workflows/stage.yml
+++ b/.github/workflows/stage.yml
@@ -104,7 +104,7 @@ jobs:
const response = await github.request('POST /repos/' + repo_name + '/releases', {
tag_name: '${{ steps.bump.outputs.version }}',
name: '${{ steps.bump.outputs.version }}',
- prerelease: true,
+ prerelease: false,
generate_release_notes: true
})
core.setOutput('upload_url', response.data.upload_url)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3aef5582c..924d1969d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -34,5 +34,5 @@
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsdk": "./node_modules/typescript/lib",
- "redhat.telemetry.enabled": true // we want to use the TS server from our node_modules folder to control its version
+ "redhat.telemetry.enabled": true
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0930c0cb..26615de29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,22 @@
# Change Log
+## 0.8.0 (Dec 11th 2023)
+- informational - Service Preview release of Red Hat Dependency Analytics (RHDA) extension.
+- informational - Configuration names for all supported executable paths in the extension settings have changed. These executable paths are only used for the analysis.
+- enhancement - Added support for error observation by using Sentry.
+- enhancement - Support for more complex SPDX SBOM relationships.
+- enhancement - Added recommendations and remediations in the _Quick Fix..._ tab.
+- fixes - Fixed an issue where unique Snyk vulnerability information was not being displayed in the Dependency Analytics report. See [PR#217](https://github.com/RHEcosystemAppEng/exhort/pull/217) for details.
+- fixes - Better valid and invalid token alert messages for the Snyk vulnerability information provider. See [PR#218](https://github.com/RHEcosystemAppEng/exhort/pull/218) for details.
+- fixes - Fixed analysis report discrepancies between Red Hat Dependency Analytics and Snyk’s analytics. See [PR#219](https://github.com/RHEcosystemAppEng/exhort/pull/219) for details.
+- fixes - Fixed the Go and Python package links so they point to their specific package manager website.
## 0.7.3 (Nov 8th 2023)
-- enhancement - Support for Golang and Python ecosystems. See [#656](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/656)
+- enhancement - Support for Golang and Python ecosystems. See [PR#656](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/656) for details.
- enhancement - A new setting for Python and Go environments to restrict package analysis when there is a package version mis-match between the environment and the manifest file. See the [Features section](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/blob/master/README.md#features) of the README for more information.
## 0.7.0 (Sep 11th 2023)
-- fixes - Improved overall performance and stability with the analysis report.
- informational - Alpha release of the new Red Hat Dependency Analytics (RHDA) extension.
-- informational - Code base refactoring from CRDA to RHDA alpha. See [#636](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/636)
+- informational - Code base refactoring from CRDA to RHDA alpha. See [PR#636](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/636) for details.
- informational - Currently no support for Python and Go, but coming soon.
+- fixes - Improved overall performance and stability with the analysis report.
## 0.3.10 (May 22th 2022)
- fixes - Extension breaks for Go version 1.17. See [#608](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/608)
- fixes - Retry failed stack analysis requests. See [#609](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/pull/609)
diff --git a/README.md b/README.md
index b981f25ef..59100e8f3 100644
--- a/README.md
+++ b/README.md
@@ -101,13 +101,23 @@ The default path is `/tmp/redhatDependencyAnalyticsReport.html`.
- **Component analysis**
Upon opening a manifest file, such as a `pom.xml`, `package.json`, `go.mod` or `requirements.txt` file, a scan starts the analysis process.
The scan provides immediate inline feedback on detected security vulnerabilities for your application's dependencies.
- Such dependencies are appropriately underlined in red, and hovering over it gives you a short summary of the security concern.
+ Such dependencies are appropriately underlined in red, and hovering over it gives you a short summary of the security concern from Snyk.
The summary has the full package name, version number, the amount of known security vulnerabilities, and the highest severity status of said vulnerabilities.
**NOTE:** Add the `target` folder to your `.gitignore` file to exclude it from Git monitoring.
![ Animated screenshot showing the inline reporting feature of Red Hat Dependency Analytics ](images/screencasts/component-analysis.gif)
+- **Recommendations and remediations**
+
After running a detailed analysis report on a specific component version, you can view recommendations and remediations by using the _Quick Fix..._ menu.
+ If there is a Red Hat recommended package version available, you can replace your version with Red Hat's version.
+
+ ![ Animated screenshot showing how to access the _Quick Fix..._ menu, and switching to a Red Hat recommended package version ](images/screencasts/quickfix.gif)
+
+
**IMPORTANT:** For Maven projects only, when analyzing a `pom.xml` file.
+ You must configure Red Hat's generally available (GA) repository to use the recommendations or remediations.
+ Add this repository, `https://maven.repository.redhat.com/ga/`, to your project's configuration.
+
- **Excluding dependencies with `exhortignore`**
You can exclude a package from analysis by marking the package for exclusion.
If you wish to ignore vulnerabilities for a dependency in a `pom.xml` file, you must add `exhortignore` as a comment against the dependency, group id, artifact id, or version scopes of that particular dependency in the manifest file.
diff --git a/images/screencasts/component-analysis.gif b/images/screencasts/component-analysis.gif
index e7b27c5fa..d8c60ef74 100644
Binary files a/images/screencasts/component-analysis.gif and b/images/screencasts/component-analysis.gif differ
diff --git a/images/screencasts/quickfix.gif b/images/screencasts/quickfix.gif
index 981294744..7c588268e 100644
Binary files a/images/screencasts/quickfix.gif and b/images/screencasts/quickfix.gif differ
diff --git a/images/screenshots/extension-workspace-settings.png b/images/screenshots/extension-workspace-settings.png
index f78f80099..0ce3916ad 100644
Binary files a/images/screenshots/extension-workspace-settings.png and b/images/screenshots/extension-workspace-settings.png differ
diff --git a/package-lock.json b/package-lock.json
index f3adaf6ec..6c44eeb79 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,17 @@
{
"name": "fabric8-analytics",
- "version": "0.7.5",
+ "version": "0.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fabric8-analytics",
- "version": "0.7.5",
+ "version": "0.8.0",
"license": "Apache-2.0",
"dependencies": {
- "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.7.1-ea.18",
+ "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.0",
"@redhat-developer/vscode-redhat-telemetry": "^0.7.0",
- "@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-ea.49",
+ "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5",
"fs": "^0.0.1-security",
"path": "^0.12.7",
"vscode-languageclient": "^8.1.0"
@@ -44,13 +44,48 @@
"vscode": "^1.76.0"
}
},
+ "../exhort-javascript-api": {
+ "name": "@RHEcosystemAppEng/exhort-javascript-api",
+ "version": "0.0.2-ea.50",
+ "extraneous": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/core": "^7.23.2",
+ "@cyclonedx/cyclonedx-library": "^4.0.0",
+ "fast-xml-parser": "^4.2.4",
+ "packageurl-js": "^1.0.2",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "exhort-javascript-api": "dist/src/cli.js"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.23.2",
+ "@openapitools/openapi-generator-cli": "^2.6.0",
+ "@types/node": "^20.3.1",
+ "babel-plugin-rewire": "^1.2.0",
+ "c8": "^8.0.0",
+ "chai": "^4.3.7",
+ "eslint": "^8.42.0",
+ "eslint-plugin-editorconfig": "^4.0.3",
+ "mocha": "^10.2.0",
+ "msw": "^1.3.2",
+ "sinon": "^15.1.2",
+ "sinon-chai": "^3.7.0",
+ "typescript": "^5.1.3"
+ },
+ "engines": {
+ "node": ">= 18.0.0",
+ "npm": ">= 9.0.0"
+ }
+ },
"../fabric8-analytics-lsp-server": {
"name": "@fabric8-analytics/fabric8-analytics-lsp-server",
- "version": "0.7.1-ea.13",
+ "version": "0.8.1-ea.6",
"extraneous": true,
"license": "Apache-2.0",
"dependencies": {
- "@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-ea.43",
+ "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.4",
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "^1.0.11",
"json-to-ast": "^2.1.0",
@@ -64,7 +99,6 @@
"@types/chai": "^4.3.7",
"@types/mocha": "^10.0.2",
"@types/node": "^20.8.4",
- "@types/node-fetch": "^2.6.6",
"@types/uuid": "^9.0.5",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
@@ -73,7 +107,9 @@
"fake-exec": "^1.1.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
+ "sinon": "^17.0.1",
"ts-node": "^10.9.1",
+ "typedoc": "^0.25.3",
"typescript": "^5.2.2"
}
},
@@ -531,12 +567,12 @@
}
},
"node_modules/@fabric8-analytics/fabric8-analytics-lsp-server": {
- "version": "0.7.1-ea.18",
- "resolved": "https://npm.pkg.github.com/download/@fabric8-analytics/fabric8-analytics-lsp-server/0.7.1-ea.18/356799181c1ee1e0db382b6ae738fcd1c5c67e5e",
- "integrity": "sha512-dJg3TpcNDkIiMNEZZusZ8cBhnMlhM4r4TmV2O52HL/7IFBKYpXheKqv6qio+2S5QNyrKJx6f/fei9qgpQDct7A==",
+ "version": "0.9.0",
+ "resolved": "https://npm.pkg.github.com/download/@fabric8-analytics/fabric8-analytics-lsp-server/0.9.0/cd862688446a5e05593ea2ac41dd18751e4f0f6c",
+ "integrity": "sha512-V8aTvDrB5bl/LIo3856r6SmJsF9IkBzyslij0DvQHVHaASyK4drpwtRNzueIMZ+eRxS64ZqAt2VmM7xXJes6SA==",
"license": "Apache-2.0",
"dependencies": {
- "@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-ea.49",
+ "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5",
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "^1.0.11",
"json-to-ast": "^2.1.0",
@@ -913,9 +949,9 @@
}
},
"node_modules/@RHEcosystemAppEng/exhort-javascript-api": {
- "version": "0.0.2-ea.49",
- "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.0.2-ea.49/0380891b685a3eb30653010a6849669553ea4bb9",
- "integrity": "sha512-APOe3QjMjE+Dx9ASZPN97Tpxq/fTvHic9IBTvfCeWhIK5M/WJ562B6U/YG7qjQmHfUur8jHXZOQpJ/bXfNBKDA==",
+ "version": "0.1.1-ea.5",
+ "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.1.1-ea.5/2cbd94284336733cbb2e3aa8c931ae6b2c1f5d8d",
+ "integrity": "sha512-VBIbeUvBw8DjTNHrBTVvqt2UrdHybApTsopXurlLWjsbmK4R8SJO0zUiQXcxDOyxY40moiNr2gncrcMN3tTRVA==",
"license": "Apache-2.0",
"dependencies": {
"@babel/core": "^7.23.2",
@@ -5071,9 +5107,9 @@
}
},
"node_modules/packageurl-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.2.0.tgz",
- "integrity": "sha512-JFoZnz1maKB0hTjn0YrmqRLgiU825SkbA370oe9ERcsKsj1EcBpe+CDo1EK9mrHc+18Hi5NmZbmXFQtP7YZEbw=="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.2.1.tgz",
+ "integrity": "sha512-cZ6/MzuXaoFd16/k0WnwtI298UCaDHe/XlSh85SeOKbGZ1hq0xvNbx3ILyCMyk7uFQxl6scF3Aucj6/EO9NwcA=="
},
"node_modules/pako": {
"version": "1.0.11",
diff --git a/package.json b/package.json
index f18c4e7d5..90326ebef 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "fabric8-analytics",
"displayName": "Red Hat Dependency Analytics",
"description": "Provides insights on security vulnerabilities in your application dependencies.",
- "version": "0.7.5",
+ "version": "0.8.0",
"author": "Red Hat",
"publisher": "redhat",
"preview": true,
@@ -187,7 +187,7 @@
"redHatDependencyAnalytics.exhortSnykToken": {
"type": "string",
"default": "",
- "description": "Red Hat Dependency Analytics server authentication token for Snyk.",
+ "description": "Red Hat Dependency Analytics authentication token for Snyk.",
"scope": "window"
},
"redHatDependencyAnalytics.matchManifestVersions": {
@@ -202,43 +202,43 @@
"description": "Path to a local file where the Red Hat Dependency Analytics report will be saved.",
"scope": "window"
},
- "mvn.executable.path": {
+ "redHatDependencyAnalytics.mvn.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of mvn executable.",
"scope": "window"
},
- "npm.executable.path": {
+ "redHatDependencyAnalytics.npm.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of npm executable.",
"scope": "window"
},
- "go.executable.path": {
+ "redHatDependencyAnalytics.go.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of go executable.",
"scope": "window"
},
- "python3.executable.path": {
+ "redHatDependencyAnalytics.python3.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of python3 executable, python3 takes precedence over python.",
"scope": "window"
},
- "pip3.executable.path": {
+ "redHatDependencyAnalytics.pip3.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of pip3 executable, pip3 takes precedence over pip.",
"scope": "window"
},
- "python.executable.path": {
+ "redHatDependencyAnalytics.python.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of python executable, python3 takes precedence over python.",
"scope": "window"
},
- "pip.executable.path": {
+ "redHatDependencyAnalytics.pip.executable.path": {
"type": "string",
"default": "",
"description": "Specifies absolute path of pip executable, pip3 takes precedence over pip.",
@@ -284,9 +284,9 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
- "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.7.1-ea.18",
+ "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.0",
"@redhat-developer/vscode-redhat-telemetry": "^0.7.0",
- "@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-ea.49",
+ "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5",
"fs": "^0.0.1-security",
"path": "^0.12.7",
"vscode-languageclient": "^8.1.0"
diff --git a/src/DepOutputChannel.ts b/src/DepOutputChannel.ts
deleted file mode 100644
index c91fa0602..000000000
--- a/src/DepOutputChannel.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-'use strict';
-
-import * as vscode from 'vscode';
-import { Titles } from './constants';
-
-export class DepOutputChannel {
- outputChannel: vscode.OutputChannel;
- constructor(channelName: string = Titles.EXT_TITLE) {
- this.outputChannel = vscode.window.createOutputChannel(channelName);
- }
-
- getOutputChannel(): vscode.OutputChannel {
- this.outputChannel.clear();
- return this.outputChannel;
- }
-
- showOutputChannel(): void {
- this.outputChannel.show();
- }
-
- clearOutputChannel(): void {
- this.outputChannel.clear();
- }
-
- addMsgOutputChannel(msg: string): void {
- this.outputChannel.append(msg);
- }
-}
diff --git a/src/caNotification.ts b/src/caNotification.ts
index 1a9eacee5..792c5136e 100644
--- a/src/caNotification.ts
+++ b/src/caNotification.ts
@@ -1,58 +1,159 @@
'use strict';
+/**
+ * Interface representing the data structure for a Component Analysis (CA) Notification.
+ */
interface CANotificationData {
- data: string;
+ errorMessage: string;
done: boolean;
uri: string;
diagCount: number;
- vulnCount: number;
+ vulnCount: Map;
}
+/**
+ * Provides functionality for generating a Component Analysis (CA) Notification.
+ */
class CANotification {
- private data: string;
- private done: boolean;
- private uri: string;
- private diagCount: number;
- private vulnCount: number;
+ private readonly errorMessage: string;
+ private readonly done: boolean;
+ private readonly uri: string;
+ private readonly diagCount: number;
+ private readonly vulnCount: Map;
+ private readonly totalVulnCount: number;
+ private static readonly VULNERABILITY = 'vulnerability';
+ private static readonly VULNERABILITIES = 'vulnerabilities';
+
+ private static readonly SYNC_SPIN = 'sync~spin';
+ private static readonly WARNING = 'warning';
+ private static readonly SHIELD = 'shield';
+ private static readonly CHECK = 'check';
+
+ /**
+ * Creates an instance of CANotification based on the given data.
+ * @param respData The data used to create the notification.
+ */
constructor(respData: CANotificationData) {
- this.data = respData.data;
+ this.errorMessage = respData.errorMessage || '';
this.done = respData.done === true;
this.uri = respData.uri;
this.diagCount = respData.diagCount || 0;
- this.vulnCount = respData.vulnCount || 0;
+ this.vulnCount = respData.vulnCount || new Map();
+ this.totalVulnCount = Object.values(this.vulnCount).reduce((sum, cv) => sum + cv, 0);
+ }
+
+ /**
+ * Determines if a singular or plural form of a word should be used based on the number of vulnerabilities.
+ * @param num The number of vulnerabilities to evaluate.
+ * @returns The appropriate string representing either 'vulnerability' or 'vulnerabilities'.
+ * @private
+ */
+ private singularOrPlural(num: number): string {
+ return num === 1 ? CANotification.VULNERABILITY : CANotification.VULNERABILITIES;
+ }
+
+ /**
+ * Capitalizes each word in a given string.
+ * @param inputString The string to be capitalized.
+ * @returns The string with each word capitalized.
+ * @private
+ */
+ private capitalizeEachWord(inputString: string): string {
+ return inputString.replace(/\b\w/g, (match) => match.toUpperCase());
+ }
+
+ /**
+ * Generates text for the total vulnerability count.
+ * @returns Text representing the total number of vulnerabilities.
+ * @private
+ */
+ private vulnCountText(): string {
+ return this.totalVulnCount > 0 ? `${this.totalVulnCount} direct ${this.singularOrPlural(this.totalVulnCount)}` : `no ${CANotification.VULNERABILITIES}`;
+ }
+
+ /**
+ * Generates the text for the in-progress status.
+ * @returns Text representing the in-progress status.
+ * @private
+ */
+ private inProgressText(): string {
+ return `$(${CANotification.SYNC_SPIN}) RHDA analysis in progress`;
+ }
+
+ /**
+ * Generates the text for the warning status for amount of vulnerabilities found.
+ * @returns Text representing the amount of vulnerabilities found.
+ * @private
+ */
+ private warningText(): string {
+ return `$(${CANotification.WARNING}) ${this.vulnCountText()} found for all the providers combined`;
+ }
+
+ /**
+ * Generates the default text for the status.
+ * @returns Default text for the status.
+ * @private
+ */
+ private defaultText(): string {
+ return `$(${CANotification.SHIELD})$(${CANotification.CHECK})`;
}
+ /**
+ * Retrieves the error message associated with the notification.
+ * @returns The error message, if available; otherwise, an empty string.
+ */
+ public errorMsg(): string {
+ return this.errorMessage;
+ }
+
+ /**
+ * Retrieves the URI associated with the notification.
+ * @returns The URI string.
+ */
public origin(): string {
return this.uri;
}
+ /**
+ * Checks if the analysis is done.
+ * @returns A boolean value indicating if the analysis is done or not.
+ */
public isDone(): boolean {
return this.done;
}
+ /**
+ * Checks if any diagnostic have been found in the analysis.
+ * @returns A boolean indicating if diagnostics have been found.
+ */
public hasWarning(): boolean {
return this.diagCount > 0;
}
+ /**
+ * Generates the text to display in the notification popup.
+ * @returns The text content for the popup notification.
+ */
public popupText(): string {
- // replace texts inside $(..)
- return this.statusText().replace(/\$\((.*?)\)/g, '');
- }
-
- private vulnCountText(): string {
- const vulns = this.vulnCount;
- return vulns > 0 ? `${vulns} ${vulns === 1 ? 'vulnerability' : 'vulnerabilities'}` : `no vulnerabilities`;
+ const text: string = Object.entries(this.vulnCount)
+ .map(([provider, vulnCount]) => `Found ${vulnCount} direct ${this.singularOrPlural(vulnCount)} for ${this.capitalizeEachWord(provider)} Provider.`)
+ .join(' ');
+ return text || this.warningText().replace(/\$\((.*?)\)/g, '');
}
+ /**
+ * Generates the text to display in the status bar.
+ * @returns The text content for the status bar.
+ */
public statusText(): string {
if (!this.isDone()) {
- return `$(sync~spin) Dependency analysis in progress`;
+ return this.inProgressText();
}
if (this.hasWarning()) {
- return `$(warning) Found ${this.vulnCountText()}`;
+ return this.warningText();
}
- return `$(shield)$(check)`;
+ return this.defaultText();
}
}
diff --git a/src/caStatusBarProvider.ts b/src/caStatusBarProvider.ts
index 44c78ced4..a47eec172 100644
--- a/src/caStatusBarProvider.ts
+++ b/src/caStatusBarProvider.ts
@@ -5,13 +5,24 @@ import { Disposable } from 'vscode-languageclient';
import { PromptText } from './constants';
import * as commands from './commands';
+/**
+ * Provides status bar functionality for the extension.
+ */
class CAStatusBarProvider implements Disposable {
private statusBarItem: StatusBarItem;
+ /**
+ * Creates an instance of the CAStatusBarProvider class.
+ */
constructor() {
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 0);
}
+ /**
+ * Displays summary information in the status bar.
+ * @param text The text to display in the status bar.
+ * @param uri The URI associated with the summary.
+ */
public showSummary(text: string, uri: string): void {
this.statusBarItem.text = text;
this.statusBarItem.command = {
@@ -23,17 +34,26 @@ class CAStatusBarProvider implements Disposable {
this.statusBarItem.show();
}
+ /**
+ * Sets an error message in the status bar indicating a failed RHDA analysis.
+ */
public setError(): void {
- this.statusBarItem.text = `$(error) Dependency analysis has failed`;
+ this.statusBarItem.text = `$(error) RHDA analysis has failed`;
this.statusBarItem.command = {
title: PromptText.LSP_FAILURE_TEXT,
command: commands.TRIGGER_STACK_LOGS,
};
}
+ /**
+ * Disposes of the status bar item.
+ */
public dispose(): void {
this.statusBarItem.dispose();
}
}
+/**
+ * Provides an instance of CAStatusBarProvider for use across the extension.
+ */
export const caStatusBarProvider: CAStatusBarProvider = new CAStatusBarProvider();
diff --git a/src/commands.ts b/src/commands.ts
index 7f5062c9b..d3600da01 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -4,13 +4,9 @@
* Commonly used commands to trigger Stack Analysis and supporting actions
*/
export const TRIGGER_FULL_STACK_ANALYSIS = 'fabric8.stackAnalysis';
-export const TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR =
- 'fabric8.stackAnalysisFromStatusBar';
-export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EXPLORER =
- 'fabric8.stackAnalysisFromExplorer';
-export const TRIGGER_FULL_STACK_ANALYSIS_FROM_PIE_BTN =
- 'fabric8.stackAnalysisFromPieBtn';
-export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EDITOR =
- 'fabric8.stackAnalysisFromEditor';
+export const TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR = 'fabric8.stackAnalysisFromStatusBar';
+export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EXPLORER = 'fabric8.stackAnalysisFromExplorer';
+export const TRIGGER_FULL_STACK_ANALYSIS_FROM_PIE_BTN = 'fabric8.stackAnalysisFromPieBtn';
+export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EDITOR = 'fabric8.stackAnalysisFromEditor';
export const TRIGGER_STACK_LOGS = 'fabric8.fabric8AnalyticsStackLogs';
-export const TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION = 'fabric8.RHRepositoryRecommendationNotification';
+export const TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION = 'fabric8.RHRepositoryRecommendationNotification';
\ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
index ed067b87d..44cb7ee40 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -2,55 +2,110 @@
import * as vscode from 'vscode';
-export function getApiConfig(): any {
- return vscode.workspace.getConfiguration('redHatDependencyAnalytics');
-}
+import { GlobalState, defaultRhdaReportFilePath } from './constants';
+import * as commands from './commands';
+import { getTelemetryId } from './redhatTelemetry';
-export function getMvnExecutable(): string {
- const mvnPath: string = vscode.workspace
- .getConfiguration('mvn.executable')
- .get('path');
- return mvnPath ? mvnPath : 'mvn';
-}
+/**
+ * Represents the configuration settings for the extension.
+ */
+class Config {
+ telemetryId: string;
+ triggerFullStackAnalysis: string;
+ triggerRHRepositoryRecommendationNotification: string;
+ utmSource: string;
+ exhortSnykToken: string;
+ matchManifestVersions: string;
+ exhortMvnPath: string;
+ exhortNpmPath: string;
+ exhortGoPath: string;
+ exhortPython3Path: string;
+ exhortPip3Path: string;
+ exhortPythonPath: string;
+ exhortPipPath: string;
+ rhdaReportFilePath: string;
-export function getNpmExecutable(): string {
- const npmPath: string = vscode.workspace
- .getConfiguration('npm.executable')
- .get('path');
- return npmPath ? npmPath : 'npm';
-}
+ private readonly DEFAULT_MVN_EXECUTABLE = 'mvn';
+ private readonly DEFAULT_NPM_EXECUTABLE = 'npm';
+ private readonly DEFAULT_GO_EXECUTABLE = 'go';
+ private readonly DEFAULT_PYTHON3_EXECUTABLE = 'python3';
+ private readonly DEFAULT_PIP3_EXECUTABLE = 'pip3';
+ private readonly DEFAULT_PYTHON_EXECUTABLE = 'python';
+ private readonly DEFAULT_PIP_EXECUTABLE = 'pip';
-export function getGoExecutable(): string {
- const goPath: string = vscode.workspace
- .getConfiguration('go.executable')
- .get('path');
- return goPath ? goPath : 'go';
-}
+ /**
+ * Creates an instance of the Config class.
+ * Initializes the instance with default extension settings.
+ */
+ constructor() {
+ this.loadData();
+ this.setProcessEnv();
+ }
-export function getPython3Executable(): string {
- const python3Path: string = vscode.workspace
- .getConfiguration('python3.executable')
- .get('path');
- return python3Path ? python3Path : 'python3';
-}
+ /**
+ * Retrieves RHDA configuration settings.
+ * @returns The RHDA configuration settings.
+ * @private
+ */
+ private getRhdaConfig(): any {
+ return vscode.workspace.getConfiguration('redHatDependencyAnalytics');
+ }
-export function getPip3Executable(): string {
- const pip3Path: string = vscode.workspace
- .getConfiguration('pip3.executable')
- .get('path');
- return pip3Path ? pip3Path : 'pip3';
-}
+ /**
+ * Loads configuration settings.
+ */
+ loadData() {
+ const rhdaConfig = this.getRhdaConfig();
-export function getPythonExecutable(): string {
- const pythonPath: string = vscode.workspace
- .getConfiguration('python.executable')
- .get('path');
- return pythonPath ? pythonPath : 'python';
-}
+ this.triggerFullStackAnalysis = commands.TRIGGER_FULL_STACK_ANALYSIS;
+ this.triggerRHRepositoryRecommendationNotification = commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION;
+ this.utmSource = GlobalState.UTM_SOURCE;
+ this.exhortSnykToken = rhdaConfig.exhortSnykToken;
+ this.matchManifestVersions = rhdaConfig.matchManifestVersions ? 'true' : 'false';
+ this.rhdaReportFilePath = rhdaConfig.redHatDependencyAnalyticsReportFilePath || defaultRhdaReportFilePath;
+ this.exhortMvnPath = rhdaConfig.mvn.executable.path || this.DEFAULT_MVN_EXECUTABLE;
+ this.exhortNpmPath = rhdaConfig.npm.executable.path || this.DEFAULT_NPM_EXECUTABLE;
+ this.exhortGoPath = rhdaConfig.go.executable.path || this.DEFAULT_GO_EXECUTABLE;
+ this.exhortPython3Path = rhdaConfig.python3.executable.path || this.DEFAULT_PYTHON3_EXECUTABLE;
+ this.exhortPip3Path = rhdaConfig.pip3.executable.path || this.DEFAULT_PIP3_EXECUTABLE;
+ this.exhortPythonPath = rhdaConfig.python.executable.path || this.DEFAULT_PYTHON_EXECUTABLE;
+ this.exhortPipPath = rhdaConfig.pip.executable.path || this.DEFAULT_PIP_EXECUTABLE;
+ }
+
+ /**
+ * Sets process environment variables based on configuration settings.
+ * @private
+ */
+ private setProcessEnv() {
+ process.env['VSCEXT_TRIGGER_FULL_STACK_ANALYSIS'] = this.triggerFullStackAnalysis;
+ process.env['VSCEXT_TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION'] = this.triggerRHRepositoryRecommendationNotification;
+ process.env['VSCEXT_UTM_SOURCE'] = this.utmSource;
+ process.env['VSCEXT_EXHORT_SNYK_TOKEN'] = this.exhortSnykToken;
+ process.env['VSCEXT_MATCH_MANIFEST_VERSIONS'] = this.matchManifestVersions;
+ process.env['VSCEXT_EXHORT_MVN_PATH'] = this.exhortMvnPath;
+ process.env['VSCEXT_EXHORT_NPM_PATH'] = this.exhortNpmPath;
+ process.env['VSCEXT_EXHORT_GO_PATH'] = this.exhortGoPath;
+ process.env['VSCEXT_EXHORT_PYTHON3_PATH'] = this.exhortPython3Path;
+ process.env['VSCEXT_EXHORT_PIP3_PATH'] = this.exhortPip3Path;
+ process.env['VSCEXT_EXHORT_PYTHON_PATH'] = this.exhortPythonPath;
+ process.env['VSCEXT_EXHORT_PIP_PATH'] = this.exhortPipPath;
+ process.env['EXHORT_DEV_MODE'] = GlobalState.EXHORT_DEV_MODE;
+ }
-export function getPipExecutable(): string {
- const pipPath: string = vscode.workspace
- .getConfiguration('pip.executable')
- .get('path');
- return pipPath ? pipPath : 'pip';
+ /**
+ * Authorizes the RHDA (Red Hat Dependency Analytics) service.
+ * @param context The extension context for authorization.
+ */
+ async authorizeRHDA(context) {
+ this.telemetryId = await getTelemetryId(context);
+ process.env['VSCEXT_TELEMETRY_ID'] = this.telemetryId;
+ }
}
+
+/**
+ * The global configuration object for the extension.
+ */
+const globalConfig = new Config();
+
+export { globalConfig };
+
diff --git a/src/constants.ts b/src/constants.ts
index 3c3decaa8..521e687a5 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -8,13 +8,13 @@ export enum GlobalState {
// to store the UTM source for tracking purposes
UTM_SOURCE = 'vscode',
// to store the current exhort environment mode
- EXHORT_DEV_MODE = 'false'
+ EXHORT_DEV_MODE = 'true'
}
export enum StatusMessages {
WIN_ANALYZING_DEPENDENCIES = 'Analyzing application dependencies...',
WIN_GENERATING_DEPENDENCIES = 'Generating Red Hat Dependency Analytics report...',
- WIN_SUCCESS_DEPENDENCY_ANALYSIS = 'Successfully generated Red Hat Dependency Analytics report...',
+ WIN_SUCCESS_DEPENDENCY_ANALYSIS = 'Successfully generated Red Hat Dependency Analytics report',
WIN_FAILURE_DEPENDENCY_ANALYSIS = 'Unable to generate Red Hat Dependency Analytics report',
WIN_SHOW_LOGS = 'No output channel has been created for Red Hat Dependency Analytics',
NO_SUPPORTED_MANIFEST = 'No supported manifest file found to be analyzed.',
@@ -39,7 +39,7 @@ export const registrationURL = 'https://app.snyk.io/signup/?utm_medium=Partner&u
// URL to Snyk webpage
export const snykURL = 'https://app.snyk.io/login?utm_campaign=Code-Ready-Analytics-2020&utm_source=code_ready&code_ready=FF1B53D9-57BE-4613-96D7-1D06066C38C9';
// default Redhat Dependency Analytics report file path
-export const defaultRedhatDependencyAnalyticsReportFilePath = '/tmp/redhatDependencyAnalyticsReport.html';
+export const defaultRhdaReportFilePath = '/tmp/redhatDependencyAnalyticsReport.html';
// Red Hat GA Repository
export const redhatMavenRepository = 'https://maven.repository.redhat.com/ga/';
// Red Hat GA Repository documentation
diff --git a/src/contextHandler.ts b/src/contextHandler.ts
deleted file mode 100644
index 60f54342d..000000000
--- a/src/contextHandler.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict';
-
-import { GlobalState } from './constants';
-import * as config from './config';
-import { getRedHatService } from '@redhat-developer/vscode-redhat-telemetry/lib';
-
-export const loadEnvironmentData = () => {
-
- const apiConfig = config.getApiConfig();
-
- process.env['VSCEXT_PROVIDE_FULLSTACK_ACTION'] = 'true';
- process.env['VSCEXT_UTM_SOURCE'] = GlobalState.UTM_SOURCE;
- process.env['VSCEXT_EXHORT_DEV_MODE'] = GlobalState.EXHORT_DEV_MODE;
- process.env['VSCEXT_EXHORT_SNYK_TOKEN'] = apiConfig.exhortSnykToken;
- process.env['VSCEXT_MATCH_MANIFEST_VERSIONS'] = apiConfig.matchManifestVersions ? 'true' : 'false';
- process.env['VSCEXT_EXHORT_MVN_PATH'] = config.getMvnExecutable();
- process.env['VSCEXT_EXHORT_NPM_PATH'] = config.getNpmExecutable();
- process.env['VSCEXT_EXHORT_GO_PATH'] = config.getGoExecutable();
- process.env['VSCEXT_EXHORT_PYTHON3_PATH'] = config.getPython3Executable();
- process.env['VSCEXT_EXHORT_PIP3_PATH'] = config.getPip3Executable();
- process.env['VSCEXT_EXHORT_PYTHON_PATH'] = config.getPythonExecutable();
- process.env['VSCEXT_EXHORT_PIP_PATH'] = config.getPipExecutable();
-};
-
-async function setTelemetryid(context) {
- const redhatService = await getRedHatService(context);
- const redhatIdProvider = await redhatService.getIdProvider();
- const redhatUuid = await redhatIdProvider.getRedHatUUID();
- process.env['VSCEXT_TELEMETRY_ID'] = redhatUuid;
-}
-
-export const loadContextData = async context => {
- try {
- await setTelemetryid(context);
-
- loadEnvironmentData();
-
- return true;
- } catch (error) {
- console.log(error);
- return false;
- }
-};
diff --git a/src/depOutputChannel.ts b/src/depOutputChannel.ts
new file mode 100644
index 000000000..09e8ed63e
--- /dev/null
+++ b/src/depOutputChannel.ts
@@ -0,0 +1,51 @@
+'use strict';
+
+import * as vscode from 'vscode';
+import { Titles } from './constants';
+
+/**
+ * Class representing an output channel for displaying messages in VS Code.
+ */
+export class DepOutputChannel {
+ outputChannel: vscode.OutputChannel;
+
+ /**
+ * Creates an instance of DepOutputChannel.
+ * @param channelName The name of the output channel. Defaults to the extension title.
+ */
+ constructor(channelName: string = Titles.EXT_TITLE) {
+ this.outputChannel = vscode.window.createOutputChannel(channelName);
+ }
+
+ /**
+ * Retrieves the VS Code OutputChannel instance.
+ * Clears the channel before returning.
+ * @returns The VS Code OutputChannel instance.
+ */
+ getOutputChannel(): vscode.OutputChannel {
+ this.outputChannel.clear();
+ return this.outputChannel;
+ }
+
+ /**
+ * Shows the output channel in the VS Code UI.
+ */
+ showOutputChannel(): void {
+ this.outputChannel.show();
+ }
+
+ /**
+ * Clears the content of the output channel.
+ */
+ clearOutputChannel(): void {
+ this.outputChannel.clear();
+ }
+
+ /**
+ * Appends a message to the output channel.
+ * @param msg The message to append to the output channel.
+ */
+ addMsgOutputChannel(msg: string): void {
+ this.outputChannel.append(msg);
+ }
+}
diff --git a/src/dependencyReportPanel.ts b/src/dependencyReportPanel.ts
index abab9c6ed..81e47606f 100644
--- a/src/dependencyReportPanel.ts
+++ b/src/dependencyReportPanel.ts
@@ -1,20 +1,19 @@
import * as vscode from 'vscode';
import * as templates from './template';
-import { Titles, defaultRedhatDependencyAnalyticsReportFilePath } from './constants';
-import * as config from './config';
+import { Titles } from './constants';
+import { globalConfig } from './config';
import * as fs from 'fs';
const loaderTmpl = templates.LOADER_TEMPLATE;
const errorTmpl = templates.ERROR_TEMPLATE;
/**
- * Manages cat coding webview panels
+ * Manages the webview panel for RHDA reports.
+ * Tracks the currently panel. Only allow a single panel to exist at a time.
*/
export class DependencyReportPanel {
- /**
- * Track the currently panel. Only allow a single panel to exist at a time.
- */
+
public static currentPanel: DependencyReportPanel | undefined;
public static readonly viewType = 'stackReport';
@@ -23,6 +22,9 @@ export class DependencyReportPanel {
private readonly _panel: vscode.WebviewPanel;
private _disposables: vscode.Disposable[] = [];
+ /**
+ * Creates or shows the webview panel.
+ */
public static createOrShowWebviewPanel() {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
@@ -85,6 +87,10 @@ export class DependencyReportPanel {
);
}
+ /**
+ * Updates the panel with the provided data.
+ * @param data Data to update the panel with.
+ */
public doUpdatePanel(data: any) {
if (data && /<\s*html[^>]*>/i.test(data)) {
DependencyReportPanel.data = data;
@@ -95,6 +101,9 @@ export class DependencyReportPanel {
}
}
+ /**
+ * Disposes the panel.
+ */
public dispose() {
DependencyReportPanel.currentPanel = undefined;
@@ -110,18 +119,35 @@ export class DependencyReportPanel {
}
}
+ /**
+ * Checks if the panel is visible.
+ * @returns A boolean indicating if the panel is visible.
+ */
public getPanelVisibility(): boolean {
return this._panel.visible;
}
+ /**
+ * Retrieves the HTML content of the webview panel.
+ * @returns The HTML content of the webview panel.
+ */
public getWebviewPanelHtml(): string {
return this._panel.webview.html;
}
+ /**
+ * Reveals the webview panel.
+ * @param column The column to reveal the panel in.
+ * @private
+ */
private _revealWebviewPanel(column: vscode.ViewColumn) {
this._panel.reveal(column);
}
+ /**
+ * Updates the webview panel content.
+ * @private
+ */
private _updateWebViewPanel() {
const output = DependencyReportPanel.data;
if (output && /<\s*html[^>]*>/i.test(output)) {
@@ -131,12 +157,16 @@ export class DependencyReportPanel {
}
}
+ /**
+ * Disposes the RHDA report file from local directory.
+ * @private
+ */
private _disposeReport() {
- const apiConfig = config.getApiConfig();
- if (fs.existsSync(apiConfig.redHatDependencyAnalyticsReportFilePath || defaultRedhatDependencyAnalyticsReportFilePath)) {
+ const reportFilePath = globalConfig.rhdaReportFilePath;
+ if (fs.existsSync(reportFilePath)) {
// Delete temp stackAnalysisReport file
- fs.unlinkSync(apiConfig.redHatDependencyAnalyticsReportFilePath || defaultRedhatDependencyAnalyticsReportFilePath);
- console.log(`File ${apiConfig.redHatDependencyAnalyticsReportFilePath || defaultRedhatDependencyAnalyticsReportFilePath} has been deleted.`);
+ fs.unlinkSync(reportFilePath);
+ console.log(`File ${reportFilePath} has been deleted.`);
}
}
}
\ No newline at end of file
diff --git a/src/stackAnalysisService.ts b/src/exhortServices.ts
similarity index 53%
rename from src/stackAnalysisService.ts
rename to src/exhortServices.ts
index b96db7078..f7d5fc325 100644
--- a/src/stackAnalysisService.ts
+++ b/src/exhortServices.ts
@@ -3,7 +3,13 @@
import * as vscode from 'vscode';
import exhort from '@RHEcosystemAppEng/exhort-javascript-api';
-export const exhortApiStackAnalysis = (pathToManifest, options) => {
+/**
+ * Performs RHDA stack analysis based on the provided manifest path and options.
+ * @param pathToManifest The path to the manifest file for analysis.
+ * @param options Additional options for the analysis.
+ * @returns A promise resolving to the stack analysis report in HTML format.
+ */
+function stackAnalysisService(pathToManifest, options) {
return new Promise(async (resolve, reject) => {
try {
// Get stack analysis in HTML format
@@ -13,9 +19,15 @@ export const exhortApiStackAnalysis = (pathToManifest, options) => {
reject(error);
}
});
-};
+}
-export const getSnykTokenValidationService = async (options) => {
+/**
+ * Performes RHDA token validation based on the provided options and displays messages based on the validation status.
+ * @param options The options for token validation.
+ * @param source The source for which the token is being validated. Example values: 'Snyk', 'OSS Index'.
+ * @returns A promise resolving after validating the token.
+ */
+async function tokenValidationService(options, source) {
try {
// Get token validation status code
@@ -24,19 +36,19 @@ export const getSnykTokenValidationService = async (options) => {
if (
tokenValidationStatus === 200
) {
- vscode.window.showInformationMessage('Snyk Token Validated Successfully');
+ vscode.window.showInformationMessage(`${source} Token Validated Successfully`);
} else if (
tokenValidationStatus === 400
) {
- vscode.window.showWarningMessage(`Missing token. Please provide a valid Snyk Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
+ vscode.window.showWarningMessage(`Missing token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
} else if (
tokenValidationStatus === 401
) {
- vscode.window.showWarningMessage(`Invalid token. Please provide a valid Snyk Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
+ vscode.window.showWarningMessage(`Invalid token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
} else if (
tokenValidationStatus === 403
) {
- vscode.window.showWarningMessage(`Forbidden. The token does not have permissions. Please provide a valid Snyk Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
+ vscode.window.showWarningMessage(`Forbidden. The token does not have permissions. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`);
} else if (
tokenValidationStatus === 429
) {
@@ -47,4 +59,6 @@ export const getSnykTokenValidationService = async (options) => {
} catch (error) {
vscode.window.showErrorMessage(`Failed to validate token, Error: ${error}`);
}
-};
\ No newline at end of file
+}
+
+export { stackAnalysisService, tokenValidationService };
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 60e21948b..afffe8851 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,35 +7,55 @@ import {
ServerOptions,
TransportKind
} from 'vscode-languageclient/node';
-
import * as path from 'path';
import * as commands from './commands';
-import { GlobalState, extensionQualifiedId, registrationURL, redhatMavenRepository, redhatMavenRepositoryDocumentationURL } from './constants';
-import * as multimanifestmodule from './multimanifestmodule';
-import { loadContextData } from './contextHandler';
+import { GlobalState, extensionQualifiedId, redhatMavenRepository, redhatMavenRepositoryDocumentationURL } from './constants';
+import { generateRHDAReport } from './stackAnalysis';
+import { globalConfig } from './config';
import { StatusMessages, PromptText } from './constants';
import { caStatusBarProvider } from './caStatusBarProvider';
import { CANotification } from './caNotification';
-import { DepOutputChannel } from './DepOutputChannel';
+import { DepOutputChannel } from './depOutputChannel';
import { record, startUp, TelemetryActions } from './redhatTelemetry';
+import { validateSnykToken } from './tokenValidation';
let lspClient: LanguageClient;
export let outputChannelDep: DepOutputChannel;
+/**
+ * Activates the extension upon launch.
+ * @param context - The extension context.
+ */
export function activate(context: vscode.ExtensionContext) {
startUp(context);
- const disposableFullStack = vscode.commands.registerCommand(
+
+ // show welcome message after first install or upgrade
+ showUpdateNotification(context);
+
+ const disposableStackAnalysisCommand = vscode.commands.registerCommand(
commands.TRIGGER_FULL_STACK_ANALYSIS,
- (uri: vscode.Uri) => {
+ async (uri: vscode.Uri) => {
+ // uri will be null in case the user has used the context menu/file explorer
+ const fileUri = uri ? uri : vscode.window.activeTextEditor.document.uri;
try {
- // uri will be null in case the user has used the context menu/file explorer
- const fileUri = uri ? uri : vscode.window.activeTextEditor.document.uri;
- multimanifestmodule.redhatDependencyAnalyticsReportFlow(context, fileUri);
+ await generateRHDAReport(context, fileUri);
+ record(context, TelemetryActions.vulnerabilityReportDone, { manifest: path.basename(fileUri.fsPath), fileName: path.basename(fileUri.fsPath) });
} catch (error) {
- // Throw a custom error message when the command execution fails
- throw new Error(`Running the contributed command: '${commands.TRIGGER_FULL_STACK_ANALYSIS}' failed.`);
+ vscode.window.showErrorMessage(error.message);
+ record(context, TelemetryActions.vulnerabilityReportFailed, { manifest: path.basename(fileUri.fsPath), fileName: path.basename(fileUri.fsPath), error: error.message });
+ }
+ }
+ );
+
+ const disposableStackLogsCommand = vscode.commands.registerCommand(
+ commands.TRIGGER_STACK_LOGS,
+ () => {
+ if (outputChannelDep) {
+ outputChannelDep.showOutputChannel();
+ } else {
+ vscode.window.showInformationMessage(StatusMessages.WIN_SHOW_LOGS);
}
}
);
@@ -51,26 +71,12 @@ export function activate(context: vscode.ExtensionContext) {
}
);
- const disposableStackLogs = vscode.commands.registerCommand(
- commands.TRIGGER_STACK_LOGS,
- () => {
- if (outputChannelDep) {
- outputChannelDep.showOutputChannel();
- } else {
- vscode.window.showInformationMessage(StatusMessages.WIN_SHOW_LOGS);
- }
- }
- );
-
registerStackAnalysisCommands(context);
- // show welcome message after first install or upgrade
- showUpdateNotification(context);
-
- loadContextData(context).then(status => {
- if (status) {
+ globalConfig.authorizeRHDA(context)
+ .then(() => {
// Create output channel
- outputChannelDep = initOutputChannel();
+ outputChannelDep = new DepOutputChannel();
// The server is implemented in node
const serverModule = context.asAbsolutePath(
path.join('dist', 'server.js')
@@ -92,7 +98,6 @@ export function activate(context: vscode.ExtensionContext) {
// Options to control the language client
const clientOptions: LanguageClientOptions = {
- // Register the server for xml, json documents
documentSelector: [
{ scheme: 'file', language: 'json' },
{ scheme: 'file', language: 'xml' },
@@ -106,11 +111,7 @@ export function activate(context: vscode.ExtensionContext) {
configurationSection: 'redHatDependencyAnalyticsServer',
// Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc'),
- },
- initializationOptions: {
- triggerFullStackAnalysis: commands.TRIGGER_FULL_STACK_ANALYSIS,
- triggerRHRepositoryRecommendationNotification: commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION
- },
+ }
};
// Create the language client and start the client.
@@ -121,14 +122,9 @@ export function activate(context: vscode.ExtensionContext) {
clientOptions
);
lspClient.start().then(() => {
- const notifiedFiles = new Set();
- const canShowPopup = (notification: CANotification): boolean => {
- const hasAlreadyShown = notifiedFiles.has(notification.origin());
- return notification.hasWarning() && !hasAlreadyShown;
- };
const showVulnerabilityFoundPrompt = async (msg: string, fileName: string) => {
- const selection = await vscode.window.showWarningMessage(`${msg}. Powered by [Snyk](${registrationURL})`, PromptText.FULL_STACK_PROMPT_TEXT);
+ const selection = await vscode.window.showWarningMessage(`${msg}`, PromptText.FULL_STACK_PROMPT_TEXT);
if (selection === PromptText.FULL_STACK_PROMPT_TEXT) {
vscode.commands.executeCommand(commands.TRIGGER_FULL_STACK_ANALYSIS);
record(context, TelemetryActions.vulnerabilityReportPopupOpened, { manifest: fileName, fileName: fileName });
@@ -141,47 +137,49 @@ export function activate(context: vscode.ExtensionContext) {
lspClient.onNotification('caNotification', respData => {
const notification = new CANotification(respData);
caStatusBarProvider.showSummary(notification.statusText(), notification.origin());
- if (canShowPopup(notification)) {
+ if (notification.hasWarning()) {
showVulnerabilityFoundPrompt(notification.popupText(), path.basename(notification.origin()));
record(context, TelemetryActions.componentAnalysisDone, { manifest: path.basename(notification.origin()), fileName: path.basename(notification.origin()) });
- // prevent further popups.
- notifiedFiles.add(notification.origin());
}
});
- lspClient.onNotification('caError', respData => {
- const notification = new CANotification(respData);
+ lspClient.onNotification('caError', errorData => {
+ const notification = new CANotification(errorData);
caStatusBarProvider.setError();
- vscode.window.showErrorMessage(respData.data);
- record(context, TelemetryActions.componentAnalysisFailed, { manifest: path.basename(notification.origin()), fileName: path.basename(notification.origin()), error: respData.data });
- });
- lspClient.onNotification('caSimpleWarning', msg => {
- vscode.window.showWarningMessage(msg);
+ // Since CA is an automated feature, only warning message will be shown on failure
+ vscode.window.showWarningMessage(notification.errorMsg());
+
+ // Record telemetry event
+ record(context, TelemetryActions.componentAnalysisFailed, { manifest: path.basename(notification.origin()), fileName: path.basename(notification.origin()), error: notification.errorMsg() });
});
});
context.subscriptions.push(
+ disposableStackAnalysisCommand,
+ disposableStackLogsCommand,
rhRepositoryRecommendationNotification,
- disposableFullStack,
- disposableStackLogs,
caStatusBarProvider,
);
- }
- });
+ })
+ .catch(error => {
+ vscode.window.showErrorMessage(`Failed to Authorize Red Hat Dependency Analytics extension: ${error.message}`);
+ throw (error);
+ });
vscode.workspace.onDidChangeConfiguration((event) => {
+
+ globalConfig.loadData();
+
if (event.affectsConfiguration('redHatDependencyAnalytics.exhortSnykToken')) {
- multimanifestmodule.triggerTokenValidation('snyk');
+ validateSnykToken();
}
- // add more token providers here...
});
}
-export function initOutputChannel(): DepOutputChannel {
- const outputChannelDepInit = new DepOutputChannel();
- return outputChannelDepInit;
-}
-
+/**
+ * Deactivates the extension.
+ * @returns A `Thenable` for void.
+ */
export function deactivate(): Thenable {
if (!lspClient) {
return undefined;
@@ -189,45 +187,58 @@ export function deactivate(): Thenable {
return lspClient.stop();
}
+/**
+ * Shows an update notification if the extension has been updated to a new version.
+ * @param context - The extension context.
+ * @returns A Promise that resolves once the notification has been displayed if needed.
+ */
async function showUpdateNotification(context: vscode.ExtensionContext) {
- // Retrive current and previous version string to show welcome message
+
const packageJSON = vscode.extensions.getExtension(extensionQualifiedId).packageJSON;
const version = packageJSON.version;
const previousVersion = context.globalState.get(GlobalState.VERSION);
- // Nothing to display
+
if (version === previousVersion) {
return;
}
- // store current version into localStorage
context.globalState.update(GlobalState.VERSION, version);
- const actions: vscode.MessageItem[] = [{ title: 'README' }, { title: 'Release Notes' }];
-
- const displayName = packageJSON.displayName;
const result = await vscode.window.showInformationMessage(
- `${displayName} has been updated to v${version} — check out what's new!`,
- ...actions
+ `${packageJSON.displayName} has been updated to v${version} — check out what's new!`,
+ 'README',
+ 'Release Notes'
);
- if (result !== null) {
- if (result === actions[0]) {
+ if (result !== undefined) {
+ if (result === 'README') {
await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(packageJSON.homepage));
- } else if (result === actions[1]) {
- await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`${packageJSON.repository.url}/releases/tag/${version}`));
+ } else if (result === 'Release Notes') {
+ await vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`${packageJSON.repository.url}/releases/tag/v${version}`));
}
}
}
+/**
+ * Registers stack analysis commands to track RHDA report generations.
+ * @param context - The extension context.
+ */
function registerStackAnalysisCommands(context: vscode.ExtensionContext) {
- const invokeFullStackReport = (uri: vscode.Uri) => {
- const fileUri = uri || vscode.window.activeTextEditor.document.uri;
- multimanifestmodule.redhatDependencyAnalyticsReportFlow(context, fileUri);
+
+ const invokeFullStackReport = async (uri: vscode.Uri) => {
+ try {
+ await generateRHDAReport(context, uri);
+ record(context, TelemetryActions.vulnerabilityReportDone, { manifest: path.basename(uri.fsPath), fileName: path.basename(uri.fsPath) });
+ } catch (error) {
+ vscode.window.showErrorMessage(error.message);
+ record(context, TelemetryActions.vulnerabilityReportFailed, { manifest: path.basename(uri.fsPath), fileName: path.basename(uri.fsPath), error: error.message });
+ }
};
const recordAndInvoke = (origin: string, uri: vscode.Uri) => {
- record(context, origin, { manifest: uri.fsPath.split('/').pop(), fileName: uri.fsPath.split('/').pop() });
- invokeFullStackReport(uri);
+ const fileUri = uri || vscode.window.activeTextEditor.document.uri;
+ record(context, origin, { manifest: fileUri.fsPath.split('/').pop(), fileName: fileUri.fsPath.split('/').pop() });
+ invokeFullStackReport(fileUri);
};
const registerCommand = (cmd: string, action: TelemetryActions) => {
@@ -242,4 +253,4 @@ function registerStackAnalysisCommands(context: vscode.ExtensionContext) {
];
context.subscriptions.push(...stackAnalysisCommands);
-}
+}
\ No newline at end of file
diff --git a/src/multimanifestmodule.ts b/src/multimanifestmodule.ts
deleted file mode 100644
index 110131217..000000000
--- a/src/multimanifestmodule.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-import * as vscode from 'vscode';
-import * as path from 'path';
-
-import * as stackanalysismodule from './stackanalysismodule';
-import { loadContextData } from './contextHandler';
-import { DependencyReportPanel } from './dependencyReportPanel';
-
-export const redhatDependencyAnalyticsReportFlow = async (context, uri) => {
- const supportedFiles = ['pom.xml', 'package.json', 'go.mod', 'requirements.txt'];
- if (
- uri.fsPath && supportedFiles.includes(path.basename(uri.fsPath))
- ) {
- stackanalysismodule.stackAnalysisLifeCycle(
- context,
- uri.fsPath
- );
- } else {
- vscode.window.showInformationMessage(
- `File ${uri.fsPath || ''} is not supported!!`
- );
- }
-};
-
-export const triggerManifestWs = context => {
- return new Promise((resolve, reject) => {
- loadContextData(context)
- .then(status => {
- if (status) {
- DependencyReportPanel.createOrShowWebviewPanel();
- resolve();
- }
- reject(`Unable to authenticate.`);
- });
- });
-};
-
-export const triggerTokenValidation = async (provider) => {
- switch (provider) {
- case 'snyk':
- stackanalysismodule.validateSnykToken();
- break;
- // case 'tidelift':
- // add Tidelift token validation here...
- // break;
- // case 'sonatype':
- // add Sonatype token validation here...
- // break;
- }
-};
\ No newline at end of file
diff --git a/src/redhatTelemetry.ts b/src/redhatTelemetry.ts
index 6cc93f020..f61c7382a 100644
--- a/src/redhatTelemetry.ts
+++ b/src/redhatTelemetry.ts
@@ -1,9 +1,14 @@
import * as vscode from 'vscode';
import { getRedHatService, TelemetryEvent, TelemetryService } from '@redhat-developer/vscode-redhat-telemetry';
-export enum TelemetryActions {
+/**
+ * Actions to be recorded for telemetry purposes.
+ */
+enum TelemetryActions {
componentAnalysisDone = 'component_analysis_done',
componentAnalysisFailed = 'component_analysis_failed',
+ vulnerabilityReportDone = 'vulnerability_report_done',
+ vulnerabilityReportFailed = 'vulnerability_report_failed',
vulnerabilityReportEditor = 'vulnerability_report_editor',
vulnerabilityReportExplorer = 'vulnerability_report_explorer',
vulnerabilityReportPopupOpened = 'vulnerability_report_popup_opened',
@@ -14,6 +19,11 @@ export enum TelemetryActions {
let telemetryServiceObj: TelemetryService = null;
+/**
+ * Retrieves the telemetry service.
+ * @param context The extension context.
+ * @returns A promise resolving to the telemetry service.
+ */
async function telemetryService(context: vscode.ExtensionContext): Promise {
if (!telemetryServiceObj) {
const redhatService = await getRedHatService(context);
@@ -22,7 +32,14 @@ async function telemetryService(context: vscode.ExtensionContext): Promise((resolve, reject) => {
+ const reportFilePath = globalConfig.rhdaReportFilePath;
+ const reportDirectoryPath = path.dirname(reportFilePath);
+
+ if (!fs.existsSync(reportDirectoryPath)) {
+ fs.mkdirSync(reportDirectoryPath, { recursive: true });
+ }
+
+ fs.writeFile(reportFilePath, data, (err) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Executes the RHDA stack analysis process.
+ * @param manifestFilePath The file path to the manifest file for analysis.
+ * @returns The stack analysis response string.
+ */
+async function executeStackAnalysis(manifestFilePath): Promise {
+ try {
+ return await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: Titles.EXT_TITLE }, async p => {
+ return new Promise(async (resolve, reject) => {
+ p.report({
+ message: StatusMessages.WIN_ANALYZING_DEPENDENCIES
+ });
+
+ // set up configuration options for the stack analysis request
+ const options = {
+ 'RHDA_TOKEN': globalConfig.telemetryId,
+ 'RHDA_SOURCE': globalConfig.utmSource,
+ 'MATCH_MANIFEST_VERSIONS': globalConfig.matchManifestVersions,
+ 'EXHORT_MVN_PATH': globalConfig.exhortMvnPath,
+ 'EXHORT_NPM_PATH': globalConfig.exhortNpmPath,
+ 'EXHORT_GO_PATH': globalConfig.exhortGoPath,
+ 'EXHORT_PYTHON3_PATH': globalConfig.exhortPython3Path,
+ 'EXHORT_PIP3_PATH': globalConfig.exhortPip3Path,
+ 'EXHORT_PYTHON_PATH': globalConfig.exhortPythonPath,
+ 'EXHORT_PIP_PATH': globalConfig.exhortPipPath
+ };
+
+ if (globalConfig.exhortSnykToken !== '') {
+ options['EXHORT_SNYK_TOKEN'] = globalConfig.exhortSnykToken;
+ }
+
+ // execute stack analysis
+ await stackAnalysisService(manifestFilePath, options)
+ .then(async (resp) => {
+ p.report({
+ message: StatusMessages.WIN_GENERATING_DEPENDENCIES
+ });
+
+ updateWebviewPanel(resp);
+
+ p.report({
+ message: StatusMessages.WIN_SUCCESS_DEPENDENCY_ANALYSIS
+ });
+
+ resolve(resp);
+ })
+ .catch(err => {
+ p.report({
+ message: StatusMessages.WIN_FAILURE_DEPENDENCY_ANALYSIS
+ });
+
+ reject(err);
+ });
+ });
+ });
+ } catch (err) {
+ updateWebviewPanel('error');
+ throw (err);
+ }
+}
+
+/**
+ * Triggers the webview panel display.
+ * @param context The extension context.
+ * @returns A Promise that resolves once the webview panel has been triggered.
+ */
+async function triggerWebviewPanel(context) {
+ await globalConfig.authorizeRHDA(context);
+ DependencyReportPanel.createOrShowWebviewPanel();
+}
+
+/**
+ * Generates the RHDA report based on the provided manifest URI.
+ * @param context The extension context.
+ * @param uri The URI of the manifest file for analysis.
+ * @returns A promise that resolves once the report generation is complete.
+ */
+async function generateRHDAReport(context, uri) {
+ if (uri.fsPath && supportedFiles.includes(path.basename(uri.fsPath))) {
+ try {
+
+ await triggerWebviewPanel(context);
+ const resp = await executeStackAnalysis(uri.fsPath);
+ if (DependencyReportPanel.currentPanel) {
+ await writeReportToFile(resp);
+ }
+
+ } catch (error) {
+ throw (error);
+ }
+ } else {
+ vscode.window.showInformationMessage(
+ `File ${uri.fsPath} is not supported!!`
+ );
+ }
+}
+
+export { generateRHDAReport };
\ No newline at end of file
diff --git a/src/stackanalysismodule.ts b/src/stackanalysismodule.ts
deleted file mode 100644
index f12559f46..000000000
--- a/src/stackanalysismodule.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-'use strict';
-
-import * as vscode from 'vscode';
-import * as path from 'path';
-import * as fs from 'fs';
-
-import * as config from './config';
-import { snykURL, defaultRedhatDependencyAnalyticsReportFilePath, StatusMessages, Titles } from './constants';
-import * as multimanifestmodule from './multimanifestmodule';
-import * as stackAnalysisServices from './stackAnalysisService';
-import { DependencyReportPanel } from './dependencyReportPanel';
-
-export const stackAnalysisLifeCycle = (
- context,
- manifestFilePath,
-) => {
- vscode.window.withProgress(
- {
- location: vscode.ProgressLocation.Window,
- title: Titles.EXT_TITLE
- },
- p => {
- return new Promise(async (resolve, reject) => {
-
- // get config data from extension workspace setting
- const apiConfig = config.getApiConfig();
-
- // create webview panel
- await multimanifestmodule.triggerManifestWs(context);
- p.report({
- message: StatusMessages.WIN_ANALYZING_DEPENDENCIES
- });
-
- // set up configuration options for the stack analysis request
- const options = {
- 'RHDA_TOKEN': process.env.VSCEXT_TELEMETRY_ID,
- 'RHDA_SOURCE': process.env.VSCEXT_UTM_SOURCE,
- 'EXHORT_DEV_MODE': process.env.VSCEXT_EXHORT_DEV_MODE,
- 'MATCH_MANIFEST_VERSIONS': apiConfig.matchManifestVersions ? 'true' : 'false',
- 'EXHORT_MVN_PATH': config.getMvnExecutable(),
- 'EXHORT_NPM_PATH': config.getNpmExecutable(),
- 'EXHORT_GO_PATH': config.getGoExecutable(),
- 'EXHORT_PYTHON3_PATH': config.getPython3Executable(),
- 'EXHORT_PIP3_PATH': config.getPip3Executable(),
- 'EXHORT_PYTHON_PATH': config.getPythonExecutable(),
- 'EXHORT_PIP_PATH': config.getPipExecutable()
- };
-
- if (apiConfig.exhortSnykToken !== '') {
- options['EXHORT_SNYK_TOKEN'] = apiConfig.exhortSnykToken;
- }
-
- // execute stack analysis
- stackAnalysisServices.exhortApiStackAnalysis(manifestFilePath, options)
- .then(resp => {
- p.report({
- message: StatusMessages.WIN_GENERATING_DEPENDENCIES
- });
- const reportFilePath = apiConfig.redHatDependencyAnalyticsReportFilePath || defaultRedhatDependencyAnalyticsReportFilePath;
- const reportDirectoryPath = path.dirname(reportFilePath);
- if (!fs.existsSync(reportDirectoryPath)) {
- fs.mkdirSync(reportDirectoryPath, { recursive: true });
- }
- fs.writeFile(reportFilePath, resp, (err) => {
- if (err) {
- reject(err);
- } else {
- if (DependencyReportPanel.currentPanel) {
- DependencyReportPanel.currentPanel.doUpdatePanel(resp);
- }
- p.report({
- message: StatusMessages.WIN_SUCCESS_DEPENDENCY_ANALYSIS
- });
- resolve(null);
- }
- });
- })
- .catch(err => {
- p.report({
- message: StatusMessages.WIN_FAILURE_DEPENDENCY_ANALYSIS
- });
- handleError(err);
- reject();
- });
- });
- }
- );
-};
-
-export const handleError = err => {
- if (DependencyReportPanel.currentPanel) {
- DependencyReportPanel.currentPanel.doUpdatePanel('error');
- }
- vscode.window.showErrorMessage(err.message);
-};
-
-export const validateSnykToken = async () => {
- const apiConfig = config.getApiConfig();
- if (apiConfig.exhortSnykToken !== '') {
-
- // set up configuration options for the token validation request
- const options = {
- 'RHDA_TOKEN': process.env.VSCEXT_TELEMETRY_ID,
- 'RHDA_SOURCE': process.env.VSCEXT_UTM_SOURCE,
- 'EXHORT_DEV_MODE': process.env.VSCEXT_EXHORT_DEV_MODE,
- 'EXHORT_SNYK_TOKEN': apiConfig.exhortSnykToken
- };
-
- // execute stack analysis
- stackAnalysisServices.getSnykTokenValidationService(options);
-
- } else {
-
- vscode.window.showInformationMessage(`Please note that if you fail to provide a valid Snyk Token in the extension workspace settings,
- Snyk vulnerabilities will not be displayed.
- To resolve this issue, please obtain a valid token from the following link: [here](${snykURL}).`);
-
- }
-};
diff --git a/src/template.ts b/src/template.ts
index bd0deefcf..29e9d22fa 100644
--- a/src/template.ts
+++ b/src/template.ts
@@ -3,7 +3,7 @@
import { Titles } from './constants';
/**
- * Commonly used templates
+ * HTML template for the loader screen during RHDA analysis.
*/
export const LOADER_TEMPLATE = `
@@ -259,6 +259,9 @@ export const LOADER_TEMPLATE = `