Add CodeScene plugin
[CodeScene](https://codescene.com/) is a multi-purpose tool bridging code, business and people. See hidden risks and social patterns in your code. Prioritize and reduce technical debt. Signed-off-by: Julio Zynger <julio.zynger@soundcloud.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-codescene': minor
|
||||
---
|
||||
|
||||
Add CodeScene plugin
|
||||
@@ -50,6 +50,8 @@ codeblocks
|
||||
Codecov
|
||||
codehilite
|
||||
Codehilite
|
||||
codescene
|
||||
CodeScene
|
||||
codemod
|
||||
codemods
|
||||
codeowners
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: CodeScene
|
||||
author: CodeScene
|
||||
authorUrl: https://codescene.com/
|
||||
category: Quality
|
||||
description: CodeScene is a multi-purpose tool bridging code, business and people. See hidden risks and social patterns in your code. Prioritize and reduce technical debt.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/codescene
|
||||
iconUrl: img/codescene_logo.svg
|
||||
npmPackageName: '@backstage/plugin-codescene'
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 151.82 151.82" style="enable-background:new 0 0 151.82 151.82;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#003263;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M37.48,128.56c1.84,0,3.1,1,3.48,2.7l1.52-0.34c-0.49-2.28-2.4-3.73-4.98-3.73c-2.95,0-5.74,1.97-5.74,6.04
|
||||
c0,4.07,2.77,6.04,5.74,6.04c2.55,0,4.39-1.42,4.98-3.64l-1.54-0.32c-0.42,1.64-1.62,2.58-3.46,2.58c-2.4,0-4.13-1.64-4.13-4.66
|
||||
C33.35,130.21,35.09,128.56,37.48,128.56z"/>
|
||||
<path class="st0" d="M48.11,130.5c-2.53,0-4.32,1.84-4.32,4.39c0,2.55,1.79,4.39,4.32,4.39c2.51,0,4.34-1.84,4.34-4.39
|
||||
C52.45,132.34,50.63,130.5,48.11,130.5z M48.11,137.99c-1.7,0-2.85-1.28-2.85-3.1c0-1.82,1.15-3.1,2.85-3.1
|
||||
c1.7,0,2.85,1.28,2.85,3.1C50.97,136.71,49.82,137.99,48.11,137.99z"/>
|
||||
<path class="st0" d="M60.67,131.85c-0.54-0.79-1.65-1.35-2.78-1.35c-2.63,0-4.08,1.89-4.08,4.39c0,2.5,1.45,4.39,4.1,4.39
|
||||
c1.25,0,2.38-0.59,2.77-1.37v1.23h1.43v-11.95h-1.43V131.85z M58.03,137.99c-1.67,0-2.77-1.23-2.77-3.1c0-1.87,1.1-3.1,2.77-3.1
|
||||
c1.67,0,2.77,1.23,2.77,3.1C60.8,136.76,59.71,137.99,58.03,137.99z"/>
|
||||
<path class="st0" d="M68.06,130.5c-2.56,0-4.12,1.97-4.12,4.39c0,2.41,1.6,4.39,4.15,4.39c1.96,0,3.37-0.96,3.8-2.62l-1.35-0.32
|
||||
c-0.25,1.06-1.15,1.67-2.45,1.67c-1.54,0-2.62-1.13-2.72-2.85h6.72c0.02-0.15,0.03-0.46,0.03-0.66
|
||||
C72.12,132.3,70.57,130.5,68.06,130.5z M65.46,133.96c0.29-1.42,1.27-2.26,2.6-2.26c1.5,0,2.41,0.96,2.55,2.26H65.46z"/>
|
||||
<path class="st0" d="M75.31,130.28c0-1.06,0.91-1.72,2.43-1.72c1.49,0,2.5,0.81,2.73,2.09l1.4-0.54c-0.3-1.75-1.97-2.92-4.13-2.92
|
||||
c-2.33,0-3.98,1.25-3.98,3.09c0,4.18,6.72,2.53,6.72,5.69c0,1.18-1.05,1.94-2.67,1.94c-1.57,0-2.83-1.08-3.07-2.73l-1.45,0.47
|
||||
c0.39,2.16,2.21,3.63,4.49,3.63c2.58,0,4.29-1.32,4.29-3.32C82.06,131.22,75.31,132.98,75.31,130.28z"/>
|
||||
<path class="st0" d="M87.61,137.99c-1.67,0-2.75-1.23-2.75-3.1c0-1.87,1.08-3.1,2.75-3.1c1.2,0,2.09,0.74,2.28,1.77l1.42-0.29
|
||||
c-0.3-1.69-1.75-2.77-3.73-2.77c-2.53,0-4.22,1.75-4.22,4.39s1.69,4.39,4.22,4.39c1.96,0,3.37-1.03,3.75-2.78l-1.4-0.29
|
||||
C89.69,137.33,88.81,137.99,87.61,137.99z"/>
|
||||
<path class="st0" d="M96.74,130.5c-2.56,0-4.12,1.97-4.12,4.39c0,2.41,1.6,4.39,4.15,4.39c1.96,0,3.37-0.96,3.8-2.62l-1.35-0.32
|
||||
c-0.25,1.06-1.15,1.67-2.45,1.67c-1.54,0-2.62-1.13-2.72-2.85h6.72c0.02-0.15,0.03-0.46,0.03-0.66
|
||||
C100.81,132.3,99.26,130.5,96.74,130.5z M94.14,133.96c0.29-1.42,1.27-2.26,2.6-2.26c1.5,0,2.41,0.96,2.55,2.26H94.14z"/>
|
||||
<path class="st0" d="M107,130.5c-1.3,0-2.33,0.73-2.92,1.74v-1.6h-1.43v8.5h1.43v-3.73c0-2.24,1.01-3.61,2.6-3.61
|
||||
c1.2,0,1.92,0.83,1.92,1.97v5.37h1.43v-5.64C110.04,131.7,108.77,130.5,107,130.5z"/>
|
||||
<path class="st0" d="M115.99,130.5c-2.56,0-4.12,1.97-4.12,4.39c0,2.41,1.6,4.39,4.15,4.39c1.96,0,3.37-0.96,3.8-2.62l-1.35-0.32
|
||||
c-0.25,1.06-1.15,1.67-2.45,1.67c-1.54,0-2.62-1.13-2.72-2.85h6.72c0.02-0.15,0.03-0.46,0.03-0.66
|
||||
C120.06,132.3,118.51,130.5,115.99,130.5z M113.39,133.96c0.29-1.42,1.27-2.26,2.6-2.26c1.5,0,2.41,0.96,2.55,2.26H113.39z"/>
|
||||
<path class="st0" d="M122.68,41.11c-6.61-18.18-23.07-28.56-46.76-28.56S35.76,22.92,29.15,41.11c-5.7,1.97-9.93,12.54-9.93,25.68
|
||||
c0,14.59,5.21,26.02,11.86,26.02c0.15,0,0.3-0.01,0.45-0.02c2.28,4.52,5.27,7.99,9.01,10.52c9.03,6.11,21.6,6.53,34.84,6.53h1.06
|
||||
c0.17,0,0.34,0,0.51,0c13.08,0,25.42-0.51,34.33-6.53c3.74-2.53,6.74-6,9.01-10.52c0.15,0.01,0.3,0.02,0.45,0.02
|
||||
c6.65,0,11.86-11.43,11.86-26.02C132.6,53.65,128.38,43.07,122.68,41.11z M21.44,66.79c0-11.22,3.1-19.51,6.69-22.53
|
||||
c-1.46,5.05-2.22,10.63-2.22,16.71c0,12.49,1.48,22.21,4.57,29.53C26.01,89.74,21.44,80.42,21.44,66.79z M119.05,90.17
|
||||
c-0.31,0.69-0.63,1.36-0.96,2c-2.08,3.97-4.75,7.05-8.06,9.29c-8.35,5.65-19.8,6.25-33.05,6.25c-0.18,0-0.36,0-0.54,0h-0.53h-0.53
|
||||
c-13.5-0.01-25.13-0.52-33.59-6.25c-3.31-2.24-5.97-5.32-8.06-9.29c-0.34-0.64-0.66-1.31-0.96-2c-3.13-7.07-4.64-16.66-4.64-29.19
|
||||
c0-6.62,0.95-12.62,2.75-17.95c0.25-0.75,0.53-1.49,0.81-2.22c6.6-16.59,22.11-26.03,44.22-26.03s37.62,9.44,44.22,26.03
|
||||
c0.29,0.73,0.56,1.46,0.81,2.22c1.8,5.33,2.75,11.33,2.75,17.95C123.69,73.51,122.18,83.09,119.05,90.17z M121.34,90.51
|
||||
c3.09-7.32,4.57-17.04,4.57-29.53c0-6.08-0.76-11.66-2.22-16.71c3.59,3.02,6.69,11.31,6.69,22.53
|
||||
C130.38,80.42,125.81,89.74,121.34,90.51z"/>
|
||||
<path class="st0" d="M75.91,28.64c-29.98,0-43.35,11.92-43.35,38.65c0,26,14.18,38.64,43.35,38.64s43.35-12.64,43.35-38.64
|
||||
C119.26,40.56,105.89,28.64,75.91,28.64z M75.91,103.7c-28.06,0-41.13-11.57-41.13-36.42c0-16.96,4.67-36.42,41.13-36.42
|
||||
s41.13,19.46,41.13,36.42C117.04,92.13,103.97,103.7,75.91,103.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
# codescene
|
||||
|
||||
[CodeScene](https://codescene.com/) is a multi-purpose tool bridging code, business and people. See hidden risks and social patterns in your code. Prioritize and reduce technical debt.
|
||||
|
||||

|
||||
|
||||
The CodeScene Backstage Plugin exposes a page component that will list the existing projects and their analysis data on your CodeScene instance.
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
1. Install the plugin by running:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/app @backstage/plugin-codescene
|
||||
```
|
||||
|
||||
2. Add the routes and pages to your `App.tsx`:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
CodescenePage,
|
||||
CodesceneProjectDetailsPage,
|
||||
} from '@backstage/plugin-codescene';
|
||||
|
||||
...
|
||||
|
||||
<Route path="/codescene" element={<CodescenePage />} />
|
||||
<Route
|
||||
path="/codescene/:projectId"
|
||||
element={<CodesceneProjectDetailsPage />}
|
||||
/>
|
||||
```
|
||||
|
||||
3. Add to the sidebar item routing to the new page:
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/components/Root/Root.tsx
|
||||
import { CodeSceneIcon } from '@backstage/plugin-codescene';
|
||||
|
||||
{
|
||||
/* other sidebar items... */
|
||||
}
|
||||
<SidebarItem icon={CodeSceneIcon} to="codescene" text="CodeScene" />;
|
||||
```
|
||||
|
||||
4. Setup the `app-config.yaml` `codescene` proxy and configuration blocks:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
'/codescene-api':
|
||||
target: '<INSTANCE_HOSTNAME>/api/v1'
|
||||
allowedMethods: ['GET']
|
||||
allowedHeaders: ['Authorization']
|
||||
headers:
|
||||
Authorization: Basic ${CODESCENE_AUTH_CREDENTIALS}
|
||||
---
|
||||
codescene:
|
||||
baseUrl: <INSTANCE_HOSTNAME>
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
## API Report File for "@backstage/plugin-codescene"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export const CodeSceneIcon: IconComponent;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CodescenePage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const codescenePlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
projectPage: RouteRef<{
|
||||
projectId: string;
|
||||
}>;
|
||||
},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CodesceneProjectDetailsPage: () => JSX.Element;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Config {
|
||||
/**
|
||||
* Configuration options for the CodeScene plugin
|
||||
*/
|
||||
codescene?: {
|
||||
/**
|
||||
* The base url of the CodeScene installation.
|
||||
* @visibility frontend
|
||||
*/
|
||||
baseUrl: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import {
|
||||
codescenePlugin,
|
||||
CodescenePage,
|
||||
CodesceneProjectDetailsPage,
|
||||
} from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(codescenePlugin)
|
||||
.addPage({
|
||||
element: <CodescenePage />,
|
||||
title: 'Root Page',
|
||||
path: '/codescene',
|
||||
})
|
||||
.addPage({
|
||||
element: <CodesceneProjectDetailsPage />,
|
||||
title: 'Details Page',
|
||||
path: '/codescene/:projectId',
|
||||
})
|
||||
.render();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 746 KiB |
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@backstage/plugin-codescene",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^1.0.0",
|
||||
"@backstage/core-components": "^0.9.3-next.1",
|
||||
"@backstage/core-plugin-api": "^1.0.0",
|
||||
"@backstage/errors": "^1.0.0",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"rc-progress": "3.2.4",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.17.0-next.2",
|
||||
"@backstage/core-app-api": "^1.0.1-next.0",
|
||||
"@backstage/dev-utils": "^1.0.1-next.0",
|
||||
"@backstage/test-utils": "^1.0.1-next.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "*",
|
||||
"@types/node": "*",
|
||||
"msw": "^0.35.0",
|
||||
"cross-fetch": "^3.1.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import {
|
||||
FetchProjectsResponse,
|
||||
Project,
|
||||
FetchAnalysesResponse,
|
||||
Analysis,
|
||||
} from './types';
|
||||
|
||||
export const codesceneApiRef = createApiRef<CodeSceneApi>({
|
||||
id: 'plugin.codescene.service',
|
||||
});
|
||||
|
||||
export interface CodeSceneApi {
|
||||
fetchProjects(): Promise<FetchProjectsResponse>;
|
||||
fetchProject(projectId: number): Promise<Project>;
|
||||
fetchAnalyses(projectId: number): Promise<FetchAnalysesResponse>;
|
||||
fetchLatestAnalysis(projectId: number): Promise<Analysis>;
|
||||
}
|
||||
|
||||
type Options = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
};
|
||||
|
||||
export class CodeSceneClient implements CodeSceneApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
}
|
||||
|
||||
async fetchProjects(): Promise<FetchProjectsResponse> {
|
||||
return this.fetchFromApi('projects');
|
||||
}
|
||||
|
||||
async fetchProject(projectId: number): Promise<Project> {
|
||||
return this.fetchFromApi(`projects/${projectId}`);
|
||||
}
|
||||
|
||||
async fetchAnalyses(projectId: number): Promise<FetchAnalysesResponse> {
|
||||
return this.fetchFromApi(`projects/${projectId}/analyses`);
|
||||
}
|
||||
|
||||
async fetchLatestAnalysis(projectId: number): Promise<Analysis> {
|
||||
return this.fetchFromApi(`projects/${projectId}/analyses/latest`);
|
||||
}
|
||||
|
||||
private async fetchFromApi(path: string): Promise<any> {
|
||||
const apiUrl = await this.getApiUrl();
|
||||
const res = await fetch(`${apiUrl}/${path}`);
|
||||
if (!res.ok) {
|
||||
throw await ResponseError.fromResponse(res);
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
private async getApiUrl() {
|
||||
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
|
||||
return `${proxyUrl}/codescene-api`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
project_owner?: string;
|
||||
}
|
||||
|
||||
export interface Analysis {
|
||||
id: number;
|
||||
name: string;
|
||||
project_id: number;
|
||||
description?: string;
|
||||
readable_analysis_time: string;
|
||||
summary: Summary;
|
||||
file_summary: FileSummary[];
|
||||
high_level_metrics: HighLevelMetrics;
|
||||
}
|
||||
|
||||
export interface Summary {
|
||||
unique_issue_ids: number;
|
||||
issues_filtered_as_outliers: number;
|
||||
entities: number;
|
||||
commits_with_issue_ids: number;
|
||||
authors_count: number;
|
||||
active_authors_count: number;
|
||||
issues_with_cycle_time: number;
|
||||
commits: number;
|
||||
issue_ids_matched_to_issues: number;
|
||||
issues_classed_as_defects: number;
|
||||
issues_with_cost: number;
|
||||
}
|
||||
|
||||
export interface FileSummary {
|
||||
language: string;
|
||||
number_of_files: number;
|
||||
blank: number;
|
||||
comment: number;
|
||||
code: number;
|
||||
}
|
||||
|
||||
export interface HighLevelMetrics {
|
||||
current_score: number;
|
||||
month_score: number;
|
||||
year_score: number;
|
||||
active_developers: number;
|
||||
lines_of_code: number;
|
||||
system_mastery: number;
|
||||
code_health_weighted_average_last_month?: number;
|
||||
code_health_month_worst_performer?: number;
|
||||
code_health_weighted_average_current?: number;
|
||||
hotspots_code_health_now_weighted_average?: number;
|
||||
code_health_weighted_average_last_year?: number;
|
||||
code_health_year_worst_performer?: number;
|
||||
hotspots_code_health_month_weighted_average?: number;
|
||||
hotspots_code_health_year_weighted_average?: number;
|
||||
code_health_now_worst_performer?: number;
|
||||
}
|
||||
|
||||
export interface FetchProjectsResponse {
|
||||
page: number;
|
||||
max_pages: number;
|
||||
projects: Project[];
|
||||
}
|
||||
|
||||
export interface FetchAnalysesResponse {
|
||||
page: number;
|
||||
max_pages: number;
|
||||
analyses: Analysis[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 21" version="1.1">
|
||||
<g id="surface1">
|
||||
<path d="M 21.898438 6.167969 C 20.5 2.242188 17.015625 0.00390625 12.003906 0.00390625 C 6.988281 0.00390625 3.5 2.242188 2.101562 6.167969 C 0.894531 6.589844 0 8.871094 0 11.707031 C 0 14.855469 1.101562 17.320312 2.511719 17.320312 C 2.542969 17.320312 2.574219 17.320312 2.605469 17.316406 C 3.089844 18.292969 3.722656 19.042969 4.511719 19.585938 C 6.425781 20.90625 9.085938 20.996094 11.886719 20.996094 L 12.113281 20.996094 C 12.148438 20.996094 12.183594 20.996094 12.21875 20.996094 C 14.988281 20.996094 17.601562 20.886719 19.488281 19.585938 C 20.277344 19.042969 20.914062 18.292969 21.394531 17.316406 C 21.425781 17.320312 21.457031 17.320312 21.488281 17.320312 C 22.898438 17.320312 24 14.855469 24 11.707031 C 24 8.871094 23.105469 6.589844 21.898438 6.167969 Z M 0.46875 11.707031 C 0.46875 9.285156 1.125 7.496094 1.886719 6.847656 C 1.578125 7.9375 1.417969 9.140625 1.417969 10.453125 C 1.417969 13.144531 1.730469 15.242188 2.382812 16.824219 C 1.4375 16.660156 0.46875 14.648438 0.46875 11.707031 Z M 21.132812 16.75 C 21.066406 16.902344 21 17.046875 20.929688 17.183594 C 20.488281 18.039062 19.921875 18.703125 19.222656 19.1875 C 17.453125 20.40625 15.03125 20.535156 12.226562 20.535156 C 12.1875 20.535156 12.148438 20.535156 12.113281 20.535156 L 11.886719 20.535156 C 9.03125 20.535156 6.566406 20.425781 4.777344 19.1875 C 4.078125 18.703125 3.515625 18.039062 3.070312 17.183594 C 3 17.046875 2.933594 16.902344 2.867188 16.75 C 2.207031 15.226562 1.886719 13.15625 1.886719 10.453125 C 1.886719 9.023438 2.085938 7.730469 2.46875 6.582031 C 2.519531 6.417969 2.582031 6.257812 2.640625 6.101562 C 4.035156 2.523438 7.320312 0.484375 12 0.484375 C 16.679688 0.484375 19.964844 2.523438 21.359375 6.101562 C 21.421875 6.257812 21.480469 6.417969 21.53125 6.582031 C 21.914062 7.730469 22.113281 9.023438 22.113281 10.453125 C 22.113281 13.15625 21.792969 15.222656 21.132812 16.75 Z M 21.617188 16.824219 C 22.269531 15.246094 22.582031 13.148438 22.582031 10.453125 C 22.582031 9.140625 22.421875 7.9375 22.113281 6.847656 C 22.875 7.5 23.53125 9.289062 23.53125 11.710938 C 23.53125 14.648438 22.5625 16.660156 21.617188 16.824219 Z M 21.617188 16.824219 "/>
|
||||
<path d="M 12 3.476562 C 5.652344 3.476562 2.824219 6.046875 2.824219 11.816406 C 2.824219 17.425781 5.824219 20.152344 12 20.152344 C 18.175781 20.152344 21.175781 17.425781 21.175781 11.816406 C 21.175781 6.046875 18.347656 3.476562 12 3.476562 Z M 12 19.671875 C 6.058594 19.671875 3.292969 17.175781 3.292969 11.8125 C 3.292969 8.152344 4.28125 3.953125 12 3.953125 C 19.71875 3.953125 20.707031 8.152344 20.707031 11.8125 C 20.707031 17.175781 17.941406 19.671875 12 19.671875 Z M 12 19.671875 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EmptyState, InfoCard } from '@backstage/core-components';
|
||||
import { Button, Grid, makeStyles, Typography } from '@material-ui/core';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { Gauge } from '../Gauge/Gauge';
|
||||
|
||||
type CodeHealthKpi = {
|
||||
name: string;
|
||||
description: string;
|
||||
value: number;
|
||||
oldValue: number;
|
||||
};
|
||||
|
||||
const CodeHealthKpiInfoCard = ({ children }: PropsWithChildren<{}>) => {
|
||||
const linkInfo = {
|
||||
title: 'What does this mean?',
|
||||
link: 'https://codescene.com/blog/3-code-health-kpis/',
|
||||
};
|
||||
return (
|
||||
<InfoCard
|
||||
title="Code Health KPIs"
|
||||
subheader="The Code Health metric identifies source code that is expensive and risky to maintain."
|
||||
deepLink={linkInfo}
|
||||
>
|
||||
{children}
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
height: '100%',
|
||||
width: 100,
|
||||
margin: '12.5px 0px 0px 50px',
|
||||
},
|
||||
});
|
||||
|
||||
export const CodeHealthKpisCard = ({
|
||||
codesceneHost,
|
||||
analysis,
|
||||
}: {
|
||||
codesceneHost: string;
|
||||
analysis: Analysis;
|
||||
}): JSX.Element => {
|
||||
const classes = useStyles();
|
||||
|
||||
if (
|
||||
!analysis.high_level_metrics.hotspots_code_health_now_weighted_average ||
|
||||
!analysis.high_level_metrics.hotspots_code_health_month_weighted_average ||
|
||||
!analysis.high_level_metrics.code_health_weighted_average_current ||
|
||||
!analysis.high_level_metrics.code_health_weighted_average_last_month ||
|
||||
!analysis.high_level_metrics.code_health_now_worst_performer ||
|
||||
!analysis.high_level_metrics.code_health_month_worst_performer
|
||||
) {
|
||||
return (
|
||||
<CodeHealthKpiInfoCard>
|
||||
<EmptyState
|
||||
missing="data"
|
||||
title="Your project does not expose Code Health KPIs"
|
||||
description="Enable the 'Full Code Health Scan' option and trigger an analysis to scan the health of all code in the project."
|
||||
action={
|
||||
<Button
|
||||
color="primary"
|
||||
href={`${codesceneHost}/projects/${analysis.project_id}/configuration#hotspots`}
|
||||
variant="contained"
|
||||
>
|
||||
Configuration
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</CodeHealthKpiInfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
const hotspotCodeHealth: CodeHealthKpi = {
|
||||
name: 'Hotspot Code Health',
|
||||
description:
|
||||
'A weighted average of the code health in your hotspots. Generally, this is the most critical metric since low code health in a hotspot will be expensive.',
|
||||
value:
|
||||
analysis.high_level_metrics.hotspots_code_health_now_weighted_average,
|
||||
oldValue:
|
||||
analysis.high_level_metrics.hotspots_code_health_month_weighted_average,
|
||||
};
|
||||
const averageCodeHealth: CodeHealthKpi = {
|
||||
name: 'Average Code Health',
|
||||
description:
|
||||
'A weighted average of all the files in the codebase. This KPI indicates how deep any potential code health issues go.',
|
||||
value: analysis.high_level_metrics.code_health_weighted_average_current,
|
||||
oldValue:
|
||||
analysis.high_level_metrics.code_health_weighted_average_last_month,
|
||||
};
|
||||
const worstPerformer: CodeHealthKpi = {
|
||||
name: 'Worst Performer',
|
||||
description:
|
||||
'A single file code health score representing the lowest code health in any module across the codebase. Points out long-term risks.',
|
||||
value: analysis.high_level_metrics.code_health_now_worst_performer,
|
||||
oldValue: analysis.high_level_metrics.code_health_month_worst_performer,
|
||||
};
|
||||
const kpis = [hotspotCodeHealth, averageCodeHealth, worstPerformer];
|
||||
const cards = kpis.map(kpi => {
|
||||
const lastMonthText =
|
||||
Math.abs(kpi.oldValue - kpi.value) < 0.1
|
||||
? undefined
|
||||
: `(from ${kpi.oldValue} last month)`;
|
||||
return (
|
||||
<div key={kpi.name}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
direction="row"
|
||||
wrap="nowrap"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item md={4}>
|
||||
<div className={classes.root}>
|
||||
<Gauge value={kpi.value} max={10} tooltipText={kpi.description} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item md={8}>
|
||||
<Typography variant="body1">{kpi.name}</Typography>
|
||||
<Typography variant="h6">
|
||||
<b>{Math.round(kpi.value * 10) / 10}</b> {lastMonthText}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return <CodeHealthKpiInfoCard>{cards}</CodeHealthKpiInfoCard>;
|
||||
};
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CodeScenePageComponent } from './CodeScenePageComponent';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { codesceneApiRef, CodeSceneClient } from '../../api/api';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
describe('CodeScenePageComponent', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
setupRequestMockHandlers(server);
|
||||
let apis: TestApiRegistry;
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
const discoveryApi: DiscoveryApi = {
|
||||
async getBaseUrl(pluginId) {
|
||||
return `http://base.com/${pluginId}`;
|
||||
},
|
||||
};
|
||||
apis = TestApiRegistry.from([
|
||||
codesceneApiRef,
|
||||
new CodeSceneClient({ discoveryApi }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CodeScenePageComponent />
|
||||
</ThemeProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/codescene': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(rendered.getByText('CodeScene')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
Header,
|
||||
Page,
|
||||
Content,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import { ProjectsComponent } from '../ProjectsComponent';
|
||||
|
||||
export const CodeScenePageComponent = () => (
|
||||
<Page themeId="tool">
|
||||
<Header
|
||||
title="CodeScene"
|
||||
subtitle="See hidden risks and social patterns in your code. Prioritize and reduce
|
||||
technical debt."
|
||||
>
|
||||
<SupportButton />
|
||||
</Header>
|
||||
<Content>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<ProjectsComponent />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CodeScenePageComponent } from './CodeScenePageComponent';
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CodeSceneProjectDetailsPage } from './CodeSceneProjectDetailsPage';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { CodeSceneApi, codesceneApiRef } from '../../api/api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
return {
|
||||
...(jest.requireActual('react-router-dom') as any),
|
||||
useParams: () => ({
|
||||
projectId: 123,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('CodesceneProjectDetailsPage', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
setupRequestMockHandlers(server);
|
||||
let apis: TestApiRegistry;
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
|
||||
const config = new ConfigReader({
|
||||
codescene: {
|
||||
baseUrl: 'www.fake-url.com',
|
||||
},
|
||||
});
|
||||
|
||||
const analysis: Analysis = {
|
||||
id: 1,
|
||||
name: 'test-project',
|
||||
project_id: 123,
|
||||
readable_analysis_time: '2022-03-22',
|
||||
summary: {
|
||||
unique_issue_ids: 0,
|
||||
issues_filtered_as_outliers: 0,
|
||||
entities: 0,
|
||||
commits_with_issue_ids: 0,
|
||||
authors_count: 0,
|
||||
active_authors_count: 0,
|
||||
issues_with_cycle_time: 0,
|
||||
commits: 0,
|
||||
issue_ids_matched_to_issues: 0,
|
||||
issues_classed_as_defects: 0,
|
||||
issues_with_cost: 0,
|
||||
},
|
||||
file_summary: [],
|
||||
high_level_metrics: {
|
||||
current_score: 0,
|
||||
month_score: 0,
|
||||
year_score: 0,
|
||||
active_developers: 0,
|
||||
lines_of_code: 0,
|
||||
system_mastery: 0,
|
||||
},
|
||||
};
|
||||
apis = TestApiRegistry.from(
|
||||
[
|
||||
codesceneApiRef,
|
||||
{
|
||||
fetchLatestAnalysis: jest.fn(() => analysis),
|
||||
} as unknown as CodeSceneApi,
|
||||
],
|
||||
[configApiRef, config],
|
||||
);
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CodeSceneProjectDetailsPage />
|
||||
</ThemeProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(rendered.getByText('CodeScene: test-project')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
GaugeCard,
|
||||
Header,
|
||||
InfoCard,
|
||||
Page,
|
||||
Progress,
|
||||
SupportButton,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { codesceneApiRef } from '../../api/api';
|
||||
import { Analysis } from '../../api/types';
|
||||
import { CodeHealthKpisCard } from '../CodeHealthKpisCard/CodeHealthKpisCard';
|
||||
|
||||
export const CodeSceneProjectDetailsPage = () => {
|
||||
const params = useParams();
|
||||
const projectId = Number(params.projectId);
|
||||
const codesceneApi = useApi(codesceneApiRef);
|
||||
const config = useApi(configApiRef);
|
||||
const {
|
||||
value: analysis,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async (): Promise<Analysis> => {
|
||||
return await codesceneApi.fetchLatestAnalysis(projectId);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
} else if (!analysis) {
|
||||
return (
|
||||
<EmptyState
|
||||
missing="content"
|
||||
title="CodeScene analysis"
|
||||
description={`No analysis found for project with id ${params.projectId}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Language',
|
||||
field: 'language',
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
title: 'Files',
|
||||
field: 'number_of_files',
|
||||
type: 'numeric',
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
field: 'code',
|
||||
type: 'numeric',
|
||||
},
|
||||
{
|
||||
title: 'Comment',
|
||||
field: 'comment',
|
||||
type: 'numeric',
|
||||
},
|
||||
];
|
||||
|
||||
const fileSummaryTable = (
|
||||
<Table
|
||||
options={{ paging: false, padding: 'dense' }}
|
||||
data={analysis.file_summary.sort((a, b) => b.code - a.code)}
|
||||
columns={columns}
|
||||
title="File Summary"
|
||||
/>
|
||||
);
|
||||
|
||||
const codesceneHost = config.getString('codescene.baseUrl');
|
||||
const analysisPath = `${codesceneHost}/${projectId}/analyses/${analysis.id}`;
|
||||
const analysisLink = { title: 'Check the analysis', link: analysisPath };
|
||||
|
||||
const analysisBody = (
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
<b>Active authors</b>: {analysis.summary.active_authors_count}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<b>Total authors</b>: {analysis.summary.authors_count}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<b>Commits</b>: {analysis.summary.commits}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header
|
||||
title={`CodeScene: ${analysis.name}`}
|
||||
subtitle={`Last analysis: ${analysis.readable_analysis_time}`}
|
||||
>
|
||||
<SupportButton>
|
||||
See hidden risks and social patterns in your code. Prioritize and
|
||||
reduce technical debt.
|
||||
</SupportButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs sm md>
|
||||
<CodeHealthKpisCard
|
||||
codesceneHost={codesceneHost}
|
||||
analysis={analysis}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
title="System Mastery"
|
||||
progress={analysis.high_level_metrics.system_mastery / 100}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
title="Current Score"
|
||||
progress={analysis.high_level_metrics.current_score / 10}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item style={{ minWidth: '460px' }}>
|
||||
<InfoCard title="Analysis" deepLink={analysisLink}>
|
||||
{analysisBody}
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>{fileSummaryTable}</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CodeSceneProjectDetailsPage } from './CodeSceneProjectDetailsPage';
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { BackstagePalette, BackstageTheme } from '@backstage/theme';
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { Circle } from 'rc-progress';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(
|
||||
theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
lineHeight: 0,
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '60%',
|
||||
transform: 'translate(-50%, -60%)',
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.textContrast,
|
||||
},
|
||||
circle: {
|
||||
width: '100%',
|
||||
transform: 'translate(10%, 0)',
|
||||
},
|
||||
colorUnknown: {},
|
||||
}),
|
||||
{ name: 'CodeSceneGauge' },
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type GaugeProps = {
|
||||
value: number;
|
||||
max: number;
|
||||
tooltipDelay?: number;
|
||||
tooltipText: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type GaugePropsGetColorOptions = {
|
||||
palette: BackstagePalette;
|
||||
value: number;
|
||||
max: number;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type GaugePropsGetColor = (args: GaugePropsGetColorOptions) => string;
|
||||
|
||||
export const getProgressColor: GaugePropsGetColor = ({
|
||||
palette,
|
||||
value,
|
||||
max,
|
||||
}) => {
|
||||
if (isNaN(value)) {
|
||||
return '#ddd';
|
||||
}
|
||||
|
||||
if (value < max / 3) {
|
||||
return palette.status.error;
|
||||
} else if (value < max * (2 / 3)) {
|
||||
return palette.status.warning;
|
||||
}
|
||||
|
||||
return palette.status.ok;
|
||||
};
|
||||
|
||||
/**
|
||||
* Circular Progress Bar
|
||||
*
|
||||
* @public
|
||||
*
|
||||
*/
|
||||
export function Gauge(props: GaugeProps) {
|
||||
const classes = useStyles(props);
|
||||
const { palette } = useTheme<BackstageTheme>();
|
||||
const [hoverRef, setHoverRef] = useState<HTMLDivElement | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const { value, max, tooltipDelay, tooltipText } = {
|
||||
...props,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const node = hoverRef;
|
||||
const handleMouseOver = () => setOpen(true);
|
||||
const handleMouseOut = () => setOpen(false);
|
||||
if (node && tooltipText) {
|
||||
node.addEventListener('mouseenter', handleMouseOver);
|
||||
node.addEventListener('mouseleave', handleMouseOut);
|
||||
|
||||
return () => {
|
||||
node.removeEventListener('mouseenter', handleMouseOver);
|
||||
node.removeEventListener('mouseleave', handleMouseOut);
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
setOpen(false);
|
||||
};
|
||||
}, [tooltipText, hoverRef]);
|
||||
|
||||
const asPercentage = (value * 100) / max;
|
||||
return (
|
||||
<div ref={setHoverRef} className={classes.root}>
|
||||
<Tooltip
|
||||
id="gauge-tooltip"
|
||||
title={tooltipText}
|
||||
placement="top"
|
||||
leaveDelay={tooltipDelay}
|
||||
onClose={() => setOpen(false)}
|
||||
open={open}
|
||||
>
|
||||
<div>
|
||||
<Circle
|
||||
strokeLinecap="butt"
|
||||
percent={asPercentage}
|
||||
strokeWidth={12}
|
||||
trailWidth={12}
|
||||
strokeColor={getProgressColor({ palette, value, max })}
|
||||
className={classes.circle}
|
||||
/>
|
||||
<div className={classes.overlay}>
|
||||
{isNaN(value) ? 'N/A' : `${Math.round(value * 10) / 10}`}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ProjectsComponent } from './ProjectsComponent';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { CodeSceneApi, codesceneApiRef } from '../../api/api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { FetchProjectsResponse, Analysis } from '../../api/types';
|
||||
|
||||
describe('ProjectsComponent', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
setupRequestMockHandlers(server);
|
||||
let apis: TestApiRegistry;
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
|
||||
const projectsResponse: FetchProjectsResponse = {
|
||||
page: 1,
|
||||
max_pages: 1,
|
||||
projects: [
|
||||
{
|
||||
id: 123,
|
||||
name: 'test-project',
|
||||
},
|
||||
],
|
||||
};
|
||||
const analysis: Analysis = {
|
||||
id: 1,
|
||||
name: 'test-project',
|
||||
project_id: 123,
|
||||
readable_analysis_time: '2022-03-22',
|
||||
summary: {
|
||||
unique_issue_ids: 0,
|
||||
issues_filtered_as_outliers: 0,
|
||||
entities: 0,
|
||||
commits_with_issue_ids: 0,
|
||||
authors_count: 0,
|
||||
active_authors_count: 0,
|
||||
issues_with_cycle_time: 0,
|
||||
commits: 0,
|
||||
issue_ids_matched_to_issues: 0,
|
||||
issues_classed_as_defects: 0,
|
||||
issues_with_cost: 0,
|
||||
},
|
||||
file_summary: [],
|
||||
high_level_metrics: {
|
||||
current_score: 0,
|
||||
month_score: 0,
|
||||
year_score: 0,
|
||||
active_developers: 0,
|
||||
lines_of_code: 0,
|
||||
system_mastery: 0,
|
||||
},
|
||||
};
|
||||
apis = TestApiRegistry.from([
|
||||
codesceneApiRef,
|
||||
{
|
||||
fetchProjects: jest.fn(() => projectsResponse),
|
||||
fetchLatestAnalysis: jest.fn(() => analysis),
|
||||
} as unknown as CodeSceneApi,
|
||||
]);
|
||||
});
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ProjectsComponent />
|
||||
</ThemeProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/codescene': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(await rendered.findByText('Projects')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('test-project')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
Progress,
|
||||
ItemCardHeader,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef, useApi } from '@backstage/core-plugin-api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { codesceneApiRef } from '../../api/api';
|
||||
import { Project, Analysis } from '../../api/types';
|
||||
import {
|
||||
Grid,
|
||||
Card,
|
||||
CardActionArea,
|
||||
Input,
|
||||
makeStyles,
|
||||
CardContent,
|
||||
Chip,
|
||||
CardActions,
|
||||
} from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
overflowXScroll: {
|
||||
overflowX: 'scroll',
|
||||
},
|
||||
}));
|
||||
|
||||
function matchFilter(filter?: string): (entry: Project) => boolean {
|
||||
const terms = filter
|
||||
?.toLocaleLowerCase('en-US')
|
||||
.split(/\s/)
|
||||
.map(e => e.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!terms?.length) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
return entry => {
|
||||
const text = `${entry.name} ${entry.description || ''}`.toLocaleLowerCase(
|
||||
'en-US',
|
||||
);
|
||||
return terms.every(term => text.includes(term));
|
||||
};
|
||||
}
|
||||
|
||||
function topLanguages(analysis: Analysis, coerceAtMost: number): string[] {
|
||||
return analysis.file_summary
|
||||
.sort((a, b) => b.code - a.code)
|
||||
.map(fileSummary => fileSummary.language)
|
||||
.slice(0, coerceAtMost);
|
||||
}
|
||||
|
||||
type ProjectAndAnalysis = {
|
||||
project: Project;
|
||||
analysis?: Analysis;
|
||||
};
|
||||
|
||||
export const ProjectsComponent = () => {
|
||||
const routeRef = useRouteRef(rootRouteRef);
|
||||
const codesceneApi = useApi(codesceneApiRef);
|
||||
|
||||
const classes = useStyles();
|
||||
const [searchText, setSearchText] = React.useState('');
|
||||
const {
|
||||
value: projectsWithAnalyses,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async (): Promise<Record<number, ProjectAndAnalysis>> => {
|
||||
const projects = (await codesceneApi.fetchProjects()).projects;
|
||||
|
||||
const promises = projects.map(project =>
|
||||
codesceneApi.fetchLatestAnalysis(project.id),
|
||||
);
|
||||
// analyses associates project IDs with their latests analysis
|
||||
const analyses: Record<number, Analysis> = await Promise.allSettled(
|
||||
promises,
|
||||
).then(results => {
|
||||
return results
|
||||
.filter(result => result.status === 'fulfilled')
|
||||
.map(result => result as PromiseFulfilledResult<Analysis>)
|
||||
.map(result => result.value)
|
||||
.reduce(
|
||||
(acc, analysis) => ({ ...acc, [analysis.project_id]: analysis }),
|
||||
{},
|
||||
);
|
||||
});
|
||||
return projects.reduce(
|
||||
(acc, project) => ({
|
||||
...acc,
|
||||
[project.id]: {
|
||||
project: project,
|
||||
analysis: analyses[project.id],
|
||||
},
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
} else if (
|
||||
!projectsWithAnalyses ||
|
||||
Object.keys(projectsWithAnalyses).length === 0
|
||||
) {
|
||||
return <Alert severity="error">No projects found!</Alert>;
|
||||
}
|
||||
|
||||
const projects = Object.values(projectsWithAnalyses)
|
||||
.map(p => p.project)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const cards = projects.filter(matchFilter(searchText)).map(project => {
|
||||
const analysis = projectsWithAnalyses[project.id].analysis;
|
||||
const subtitle = analysis
|
||||
? `Last analysis: ${analysis.readable_analysis_time} · Score: ${analysis.high_level_metrics.current_score} · Active authors: ${analysis.high_level_metrics.active_developers}`
|
||||
: undefined;
|
||||
const chips = analysis
|
||||
? topLanguages(analysis, 3).map(lang => (
|
||||
<Chip label={lang} key={lang} size="small" />
|
||||
))
|
||||
: undefined;
|
||||
return (
|
||||
<Grid key={project.id} item xs={3}>
|
||||
<Card>
|
||||
<CardActionArea
|
||||
style={{
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
component={RouterLink}
|
||||
to={`${routeRef()}/${project.id}`}
|
||||
>
|
||||
<ItemCardHeader title={project.name} />
|
||||
<CardContent>{subtitle}</CardContent>
|
||||
<CardActions disableSpacing>{chips}</CardActions>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Content className={classes.overflowXScroll}>
|
||||
<ContentHeader title="Projects">
|
||||
<Input
|
||||
id="projects-filter"
|
||||
type="search"
|
||||
placeholder="Filter"
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
/>
|
||||
</ContentHeader>
|
||||
<Grid container>{cards}</Grid>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { ProjectsComponent } from './ProjectsComponent';
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export {
|
||||
codescenePlugin,
|
||||
CodescenePage,
|
||||
CodesceneProjectDetailsPage,
|
||||
} from './plugin';
|
||||
import CodeSceneIconComponent from './assets/codescene.icon.svg';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const CodeSceneIcon: IconComponent =
|
||||
CodeSceneIconComponent as IconComponent;
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { codescenePlugin } from './plugin';
|
||||
|
||||
describe('codescene', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(codescenePlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
createApiFactory,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { codesceneApiRef, CodeSceneClient } from './api/api';
|
||||
import { rootRouteRef, projectDetailsRouteRef } from './routes';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const codescenePlugin = createPlugin({
|
||||
id: 'codescene',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: codesceneApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef },
|
||||
factory: ({ discoveryApi }) => new CodeSceneClient({ discoveryApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
projectPage: projectDetailsRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const CodescenePage = codescenePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'CodescenePage',
|
||||
component: () =>
|
||||
import('./components/CodeScenePageComponent').then(
|
||||
m => m.CodeScenePageComponent,
|
||||
),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const CodesceneProjectDetailsPage = codescenePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'CodesceneProjectDetailsPage',
|
||||
component: () =>
|
||||
import('./components/CodeSceneProjectDetailsPage').then(
|
||||
m => m.CodeSceneProjectDetailsPage,
|
||||
),
|
||||
mountPoint: projectDetailsRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'codescene',
|
||||
});
|
||||
|
||||
export const projectDetailsRouteRef = createRouteRef({
|
||||
id: 'codescene-project-page',
|
||||
params: ['projectId'],
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
@@ -243,6 +243,7 @@ const NO_WARNING_PACKAGES = [
|
||||
'plugins/catalog-common',
|
||||
'plugins/catalog-graph',
|
||||
'plugins/catalog-react',
|
||||
'plugins/codescene',
|
||||
'plugins/graphiql',
|
||||
'plugins/org',
|
||||
'plugins/periskop',
|
||||
|
||||
Reference in New Issue
Block a user