+
+The SonarQube Issues plugin shows open SonarQube issues associated with the SonarQube project specified in the entity's `cortex.yaml`. If the `cortex.yaml` has a [SonarQube Project key](https://docs.cortex.io/docs/reference/integrations/sonarqube#entity-descriptor) defined in its `x-cortex-static-analysis` configuration, it will query for issues pertaining to that project. For example:
+
+```yaml
+openapi: 3.0.1
+info:
+ title: Funrepo
+ description: it is a fun repo
+ x-cortex-git:
+ github:
+ alias: cortex
+ repository: martindstone-org/funrepo
+ x-cortex-tag: funrepo
+ x-cortex-type: service
+ x-cortex-static-analysis:
+ sonarqube:
+ project: martindstone-org_funrepo
+```
+
+## Setup
+
+This plugin requires a proxy to SonarQube. To set up:
+
+- Create a token in SonarQube by clicking on your profile > My Account > Security
+- In Cortex, define a secret whose value is your new token. Name it `sonarqube_plugin`.
+- Create a proxy:
+
+ - Navigate to Plugins, then click on the Proxies tab, then click on Create Proxy
+ - Give the proxy a name, like SonarQube Proxy, then click on Add URL.
+ - For the URL Prefix, type in the API base URL of your SonarQube instance. The default for cloud is `https://sonarcloud.io`. **If you are self-hosting SonarQube, you will have to put in your own base URL instead.**
+ - Click on Add Header and add a header whose name is `Authorization` and whose value is `Bearer {{{secrets.sonarqube_plugin}}}` (include the curly braces!)
+
+- Once you are done, the proxy should look like the below:
+
+
+
+### Self-Hosted setup
+
+The plugin uses `https://sonarcloud.io` as its default API base URL. If you are self-hosting Sonarqube, then you will have a different URL. To configure the plugin to use that URL, you can create a Sonarqube plugin configuration entity in Cortex with your own API base URL.
+
+- Consider creating a new entity type, so that any existing scorecards are not affected by ths configuration entity. In this example, we have created a new entity type called `plugin-configuration`
+- Create a new entity with the tag `sonarqube-plugin-config`
+- Set `x-cortex-definition.sonarqube-url` to the value of your ServiceNow Instance URL. For example, if my Sonarqube API base URL was `https://sonarqube.martindstone.com`, my `sonarqube-plugin-config` entity would look like this:
+
+```yaml
+openapi: 3.0.1
+info:
+ title: Sonarqube Plugin Config
+ description: ""
+ x-cortex-tag: sonarqube-plugin-config
+ x-cortex-type: plugin-configuration
+ x-cortex-definition:
+ sonarqube-url: https://sonarqube.martindstone.com
+```
+
+Now, you can build and add the plugin.
+
+- Build the plugin:
+ - Make sure you have npm or yarn.
+ - In your terminal, in the `sonarqube-issues` directory, type `yarn` or `npm install` to install the dependencies; then type `npm run build` or `yarn build` to build the plugin.
+- The compiled plugin will be created in `dist/ui.html`.
+- In Plugins > All, click **Register Plugin**.
+- Give the plugin a name, like SonarQube Issues. This is the name users will see in the plugin listing.
+- Under **Associated Proxy**, choose the proxy you just created.
+- Under **Plugin Context**, click on Add another context; choose Selection type: Include, and Entity types: service.
+- This plugin does not work in the Global context. Turn off the switch labeled **Include in global context**.
+- In The **Plugin code** section, upload the `dist/ui.html` file you just built.
+- Click on **Save plugin**.
+
+### Create a plugin configuration entity (self-hosted only)
+
+This plugin will connect to SonarQube's cloud instance out of the box, so if you are using SonarQube in the cloud, you should skip this step. If you are self-hosting SonarQube and need to direct the plugin to a different REST API endpoint, create a plugin configuration entity with your SonarQube REST API base URL as follows:
+
+- Consider creating a new entity type, so that any existing scorecards are not affected by this configuration entity. In this example, we have created a new entity type called `plugin-configuration`
+- Create a new entity with the tag `sonarqube-plugin-config`
+- Set `x-cortex-definition.sonarqube-url` to the value of your SonarQube API base URL. For example, if my SonarQube API base URL was `https://sonarqube.martindstone.com`, my `sonarqube-plugin-config` entity would look like this:
+
+```
+openapi: 3.0.1
+info:
+ title: SonarQube Plugin Config
+ description: ""
+ x-cortex-tag: sonarqube-plugin-config
+ x-cortex-type: plugin-configuration
+ x-cortex-definition:
+ sonarqube-url: https://sonarqube.martindstone.com
+```
+
+Now, when you navigate to a Cortex service that has a SonarQube project associated with it, you should be able to click on Plugins > SonarQube Issues and see the SonarQube Issues associated with the project that is linked to the service.
+
+## Troubleshooting
+
+### Getting a message that "No SonarQube details were found for this entity"
+
+If you get the following message:
+
+
+
+This means that the plugin did not find a SonarQube project defined as described [here](https://docs.cortex.io/docs/reference/integrations/sonarqube#entity-descriptor).
+
+# Setting up your dev environment
+
+SonarQube Issues Cortex Plugin is a [Cortex](https://www.cortex.io/) plugin. To see how to run the plugin inside of Cortex, see [our docs](https://docs.cortex.io/docs/plugins).
+
+### Prerequisites
+
+Developing and building this plugin requires either [yarn](https://classic.yarnpkg.com/lang/en/docs/install/) or [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
+
+## Getting started
+
+1. Run `yarn` or `npm install` to download all dependencies
+2. Run `yarn build` or `npm run build` to compile the plugin code into `./dist/ui.html`
+3. Upload `ui.html` into Cortex on a create or edit plugin page
+4. Add or update the code and repeat steps 2-3 as necessary
+
+### Notable scripts
+
+The following commands come pre-configured in this repository. You can see all available commands in the `scripts` section of [package.json](./package.json). They can be run with npm via `npm run {script_name}` or with yarn via `yarn {script_name}`, depending on your package manager preference. For instance, the `build` command can be run with `npm run build` or `yarn build`.
+
+- `build` - compiles the plugin. The compiled code root is `./src/index.tsx` (or as defined by [webpack.config.js](webpack.config.js)) and the output is generated into `dist/ui.html`.
+- `test` - runs all tests defined in the repository using [jest](https://jestjs.io/)
+- `lint` - runs lint and format checking on the repository using [prettier](https://prettier.io/) and [eslint](https://eslint.org/)
+- `lintfix` - runs eslint in fix mode to fix any linting errors that can be fixed automatically
+- `formatfix` - runs Prettier in fix mode to fix any formatting errors that can be fixed automatically
+
+### Available React components
+
+See available UI components via our [Storybook](https://cortexapps.github.io/plugin-core/).
diff --git a/plugins/sonarqube-issues/__mocks__/fileMock.js b/plugins/sonarqube-issues/__mocks__/fileMock.js
new file mode 100644
index 0000000..0a445d0
--- /dev/null
+++ b/plugins/sonarqube-issues/__mocks__/fileMock.js
@@ -0,0 +1 @@
+module.exports = "test-file-stub";
diff --git a/plugins/sonarqube-issues/__mocks__/styleMock.js b/plugins/sonarqube-issues/__mocks__/styleMock.js
new file mode 100644
index 0000000..f053ebf
--- /dev/null
+++ b/plugins/sonarqube-issues/__mocks__/styleMock.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/plugins/sonarqube-issues/babel.config.js b/plugins/sonarqube-issues/babel.config.js
new file mode 100644
index 0000000..1442fdf
--- /dev/null
+++ b/plugins/sonarqube-issues/babel.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ plugins: ["@babel/plugin-syntax-jsx"],
+ presets: [
+ ["@babel/preset-env", { targets: { node: "current" } }],
+ "@babel/preset-typescript",
+ ["@babel/preset-react", { runtime: "automatic" }],
+ ],
+};
diff --git a/plugins/sonarqube-issues/img/sonarqube-no-project.png b/plugins/sonarqube-issues/img/sonarqube-no-project.png
new file mode 100644
index 0000000..1c69e17
Binary files /dev/null and b/plugins/sonarqube-issues/img/sonarqube-no-project.png differ
diff --git a/plugins/sonarqube-issues/img/sonarqube-plugin.png b/plugins/sonarqube-issues/img/sonarqube-plugin.png
new file mode 100644
index 0000000..67fa08f
Binary files /dev/null and b/plugins/sonarqube-issues/img/sonarqube-plugin.png differ
diff --git a/plugins/sonarqube-issues/img/sonarqube-proxy.png b/plugins/sonarqube-issues/img/sonarqube-proxy.png
new file mode 100644
index 0000000..fbfc3ab
Binary files /dev/null and b/plugins/sonarqube-issues/img/sonarqube-proxy.png differ
diff --git a/plugins/sonarqube-issues/img/sonarqube-token.png b/plugins/sonarqube-issues/img/sonarqube-token.png
new file mode 100644
index 0000000..44805f6
Binary files /dev/null and b/plugins/sonarqube-issues/img/sonarqube-token.png differ
diff --git a/plugins/sonarqube-issues/jest.config.js b/plugins/sonarqube-issues/jest.config.js
new file mode 100644
index 0000000..e7cc6d7
--- /dev/null
+++ b/plugins/sonarqube-issues/jest.config.js
@@ -0,0 +1,18 @@
+module.exports = {
+ moduleNameMapper: {
+ // map static asset imports to a stub file under the assumption they are not important to our tests
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
+ "/__mocks__/fileMock.js",
+ // map style asset imports to a stub file under the assumption they are not important to our tests
+ "\\.(css|less)$": "/__mocks__/styleMock.js",
+ "@cortexapps/plugin-core/components":
+ "/../../node_modules/@cortexapps/plugin-core/dist/components.cjs.js",
+ "@cortexapps/plugin-core":
+ "/../../node_modules/@cortexapps/plugin-core/dist/index.cjs.js",
+ },
+ setupFilesAfterEnv: ["/setupTests.ts"],
+ testEnvironment: "jsdom",
+ transform: {
+ "^.+\\.tsx?$": "babel-jest",
+ },
+};
diff --git a/plugins/sonarqube-issues/package.json b/plugins/sonarqube-issues/package.json
new file mode 100644
index 0000000..84f2386
--- /dev/null
+++ b/plugins/sonarqube-issues/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "SonarQube-Issues-Cortex-Plugin",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@chakra-ui/react": "2",
+ "@cortexapps/plugin-core": "^2.0.0",
+ "@emotion/react": "^11.13.3",
+ "@emotion/styled": "^11.13.0",
+ "framer-motion": "^11.11.17",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.21.3",
+ "@babel/plugin-syntax-jsx": "^7.18.6",
+ "@babel/preset-env": "^7.20.2",
+ "@babel/preset-react": "^7.18.6",
+ "@babel/preset-typescript": "^7.21.0",
+ "@popperjs/core": "^2.11.8",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^14.0.0",
+ "@types/react": "^18.0.28",
+ "@types/react-dom": "^18.0.11",
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
+ "@typescript-eslint/parser": "^5.55.0",
+ "babel-jest": "^29.5.0",
+ "css-loader": "^6.7.3",
+ "eslint": "^8.0.1",
+ "eslint-config-prettier": "^8.7.0",
+ "eslint-config-standard-with-typescript": "^34.0.0",
+ "eslint-plugin-import": "^2.25.2",
+ "eslint-plugin-n": "^15.6.1",
+ "eslint-plugin-promise": "^6.0.0",
+ "eslint-plugin-react": "^7.32.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "html-webpack-plugin": "^5.5.0",
+ "jest": "^29.5.0",
+ "jest-environment-jsdom": "^29.5.0",
+ "jest-fetch-mock": "^3.0.3",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.8.4",
+ "prop-types": "^15.8.1",
+ "react-dev-utils": "^12.0.1",
+ "style-loader": "^3.3.1",
+ "terser-webpack-plugin": "^5.3.7",
+ "ts-loader": "^9.4.2",
+ "typescript": "^4.9.5",
+ "url-loader": "^4.1.1",
+ "webpack": "^5.76.1",
+ "webpack-cli": "^5.0.1",
+ "webpack-dev-server": "^4.15.0"
+ },
+ "scripts": {
+ "build": "webpack --mode=production",
+ "clean": "rm -r ./dist",
+ "dev": "webpack serve --mode=development",
+ "fix": "run-p formatfix lintfix",
+ "formatfix": "yarn prettier . --write",
+ "formatcheck": "yarn prettier . --check",
+ "lint": "run-p formatcheck lintcheck",
+ "lintcheck": "yarn eslint src",
+ "lintfix": "yarn lintcheck --fix",
+ "test": "jest"
+ }
+}
diff --git a/plugins/sonarqube-issues/setupTests.ts b/plugins/sonarqube-issues/setupTests.ts
new file mode 100644
index 0000000..fa0a10d
--- /dev/null
+++ b/plugins/sonarqube-issues/setupTests.ts
@@ -0,0 +1,60 @@
+import "@testing-library/jest-dom/extend-expect";
+import fetchMock from "jest-fetch-mock";
+
+fetchMock.enableMocks();
+
+const mockContext = {
+ apiBaseUrl: "https://api.getcortexapp.com",
+ entity: {
+ definition: null,
+ description: null,
+ groups: null,
+ name: "Inventory planner",
+ ownership: {
+ emails: [
+ {
+ description: null,
+ email: "nikhil@cortex.io",
+ inheritance: null,
+ id: 1,
+ },
+ ],
+ },
+ tag: "inventory-planner",
+ type: "service",
+ },
+ location: "ENTITY",
+ user: {
+ email: "ganesh@cortex.io",
+ name: "Ganesh Datta",
+ role: "ADMIN",
+ },
+};
+
+jest.mock("@cortexapps/plugin-core/components", () => {
+ const originalModule = jest.requireActual(
+ "@cortexapps/plugin-core/components"
+ );
+ return {
+ ...originalModule,
+ usePluginContext: () => {
+ return mockContext;
+ },
+ PluginProvider: ({ children }) => {
+ return children;
+ },
+ };
+});
+
+jest.mock("@cortexapps/plugin-core", () => {
+ const originalModule = jest.requireActual("@cortexapps/plugin-core");
+ return {
+ ...originalModule,
+ CortexApi: {
+ ...originalModule.CortexApi,
+ getContext: () => {
+ return mockContext;
+ },
+ },
+ };
+});
diff --git a/plugins/sonarqube-issues/src/api/Cortex.ts b/plugins/sonarqube-issues/src/api/Cortex.ts
new file mode 100644
index 0000000..01a0d12
--- /dev/null
+++ b/plugins/sonarqube-issues/src/api/Cortex.ts
@@ -0,0 +1,8 @@
+export const getEntityYaml = async (
+ baseUrl: string,
+ entityTag: string
+): Promise> => {
+ const res = await fetch(`${baseUrl}/catalog/${entityTag}/openapi`);
+
+ return await res.json();
+};
diff --git a/plugins/sonarqube-issues/src/baseStyles.css b/plugins/sonarqube-issues/src/baseStyles.css
new file mode 100644
index 0000000..e9c5e5f
--- /dev/null
+++ b/plugins/sonarqube-issues/src/baseStyles.css
@@ -0,0 +1,3 @@
+body {
+ font: 14px sans-serif;
+}
diff --git a/plugins/sonarqube-issues/src/components/App.test.tsx b/plugins/sonarqube-issues/src/components/App.test.tsx
new file mode 100644
index 0000000..09a0ad9
--- /dev/null
+++ b/plugins/sonarqube-issues/src/components/App.test.tsx
@@ -0,0 +1,15 @@
+import { render } from "@testing-library/react";
+import App from "./App";
+import { waitForLoading } from "../../../testUtils/testUtils";
+
+fetchMock.mockResponse(JSON.stringify({}));
+describe("App", () => {
+ it("verifies that the plugin works", async () => {
+ render();
+
+ expect(fetch).toHaveBeenCalledWith(
+ "https://api.getcortexapp.com/catalog/inventory-planner/openapi"
+ );
+ await waitForLoading();
+ });
+});
diff --git a/plugins/sonarqube-issues/src/components/App.tsx b/plugins/sonarqube-issues/src/components/App.tsx
new file mode 100644
index 0000000..7638ea1
--- /dev/null
+++ b/plugins/sonarqube-issues/src/components/App.tsx
@@ -0,0 +1,84 @@
+import type React from "react";
+import { Box, PluginProvider } from "@cortexapps/plugin-core/components";
+import { ChakraProvider } from "@chakra-ui/react";
+import "../baseStyles.css";
+import ErrorBoundary from "./ErrorBoundary";
+import CortexEntity from "./EntityInfo";
+
+const App: React.FC = () => {
+ return (
+
+
+
+
+
+
+