Skip to content

Commit

Permalink
Sonar integration
Browse files Browse the repository at this point in the history
  • Loading branch information
llima committed Aug 26, 2021
1 parent 6760ee8 commit 8590a6e
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 38 deletions.
6 changes: 3 additions & 3 deletions front/src/components/code-quality/code-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ class CodePanel extends React.Component<ICodePanelProps, ICodePanelState> {
var items = that.state.currentCode.components;

if (checked)
items.push(component)
items.push(component.key)
else
items = items.filter(d => d.key !== component.key);
items = items.filter(d => d !== component.key);

that.setState(prevState => ({
currentCode: { ...prevState.currentCode, components: items }
Expand Down Expand Up @@ -194,7 +194,7 @@ class CodePanel extends React.Component<ICodePanelProps, ICodePanelState> {
{components.map(item => (
<Checkbox
onChange={(event, checked) => (this.addComponent(item, checked, this))}
checked={currentCode.components.filter(d => d.key === item.key).length > 0}
checked={currentCode.components.filter(d => d === item.key).length > 0}
label={item.key}
/>
))}
Expand Down
2 changes: 1 addition & 1 deletion front/src/model/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface ICode {
type: string;
server: string;
token: string;
components?: ISonarComponent[];
components?: string[];
}


1 change: 1 addition & 0 deletions front/src/model/sonar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ISonarBranch {
status: any;
measures: ISonarMeasure[]
isShow: boolean;
link: string;
}

export interface ISonarMeasure {
Expand Down
2 changes: 1 addition & 1 deletion front/src/model/stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const StackValues: IStack[] = [
},
{
id: 4,
text: "SQLServer"
text: "Type Script"
}
];

Expand Down
32 changes: 24 additions & 8 deletions front/src/pages/code-quality/code-page-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Status, Statuses, StatusSize } from "azure-devops-ui/Status";
import { AiFillBug } from "react-icons/ai";
import { FaRadiationAlt } from "react-icons/fa";
import { GiCheckedShield, GiPadlock } from "react-icons/gi";
import { ISonarBranch, ISonarMeasure } from "../../model/sonar";
import { ISonarBranch, ISonarMeasure } from "../../model/sonar";


export const projectsMock =
Expand Down Expand Up @@ -55,7 +55,7 @@ export const projectsMock =

export function renderBranchStatus(branch: ISonarBranch, className?: string) {

if(!branch)
if (!branch)
return <Status {...Statuses.Canceled} className={className} size={StatusSize.l} />;

if (branch.status.qualityGateStatus === "ERROR")
Expand All @@ -65,38 +65,54 @@ export function renderBranchStatus(branch: ISonarBranch, className?: string) {

};

export function configureMeasure(measure: ISonarMeasure): any {
export function configureMeasure(measure: ISonarMeasure, measures: ISonarMeasure[]): any {

var status: string = "";
var ratings: ISonarMeasure[] = [];

switch (measure.metric) {

case "vulnerabilities":
ratings = measures.filter(d => d.metric === "security_rating");
if (ratings.length > 0)
status = ratings[0].value.replace(".0", "");
return {
label: "Vulnerabilities",
value: measure.value,
status: "A",
status: status,
icon: <GiPadlock className="icon-tools" />
};

case "bugs":
ratings = measures.filter(d => d.metric === "reliability_rating");
if (ratings.length > 0)
status = ratings[0].value.replace(".0", "");
return {
label: "Bugs",
value: measure.value,
status: "C",
status: status,
icon: <AiFillBug className="icon-tools" />
};

case "security_hotspots_reviewed":
ratings = measures.filter(d => d.metric === "security_review_rating");
if (ratings.length > 0)
status = ratings[0].value.replace(".0", "");
return {
label: "Hotspots",
value: measure.value + "%",
status: "E",
status: status,
icon: <GiCheckedShield className="icon-tools" />
};

case "code_smells":
ratings = measures.filter(d => d.metric === "sqale_rating");
if (ratings.length > 0)
status = ratings[0].value.replace(".0", "");
return {
label: "Code Smells",
value: measure.value,
status: "A",
status: status,
icon: <FaRadiationAlt className="icon-tools" />
};

Expand All @@ -106,7 +122,7 @@ export function configureMeasure(measure: ISonarMeasure): any {
value: measure.value + "%",
};


case "duplicated_lines_density":
return {
label: "Duplications",
Expand Down
53 changes: 46 additions & 7 deletions front/src/pages/code-quality/code-page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,59 @@ button, input, select, textarea {
margin-left: 10px;
float: left;

&--A {
&--1 {
background-color: green !important;
position: relative;
&::after {
display: block;
content: 'A';
position: absolute;
left: 8px;
}
}

&--C {
background-color: orange !important;
&--2 {
background-color: #b0d513 !important;
position: relative;
&::after {
display: block;
content: 'B';
position: absolute;
left: 8px;
}
}

&--E {
background-color: red !important;
&--3 {
background-color: #eabe06 !important;
position: relative;
&::after {
display: block;
content: 'C';
position: absolute;
left: 8px;
}
}

&--S {
background-color: blue !important;
&--4 {
background-color: #ed7d20 !important;
position: relative;
&::after {
display: block;
content: 'D';
position: absolute;
left: 8px;
}
}

&--5 {
background-color: red !important;
position: relative;
&::after {
display: block;
content: 'E';
position: absolute;
left: 8px;
}
}

}
Expand Down
55 changes: 38 additions & 17 deletions front/src/pages/code-quality/code-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
import { Button } from 'azure-devops-ui/Button';
import { ButtonGroup } from 'azure-devops-ui/ButtonGroup';
import { Page } from 'azure-devops-ui/Page';
import { ObservableValue } from "azure-devops-ui/Core/Observable";
import { Card } from 'azure-devops-ui/Card';
import { Pill } from 'azure-devops-ui/Components/Pill/Pill';

Expand All @@ -27,7 +26,7 @@ import { MessageCard, MessageCardSeverity } from 'azure-devops-ui/MessageCard';
import { configureMeasure, renderBranchStatus, } from './code-page-settings';

import CodePanel from '../../components/code-quality/code-panel';
import { Spinner } from '@fluentui/react';
import { Link, Spinner } from '@fluentui/react';
import { ZeroData, ZeroDataActionType } from 'azure-devops-ui/ZeroData';
import { CodeServiceId, ICodeService } from '../../services/code';

Expand Down Expand Up @@ -59,28 +58,40 @@ class Code extends React.Component<{}, ICodeState> {

async loadComponents() {

this.setState({ components: [], loading: true, settingsExpanded: false });

var selectedTabs: string[] = [];
var server: string = "";
var token: string = "";
var projects: string[] = [];

try {

var configs = await this.codeService.getCode();

if (configs.length > 0) {
var config = configs[0];
var config = configs[configs.length-1];
server = config.server;
token = config.token;
projects = config.components;
}

if (projects.length > 0) {

var components = await this.sonarService.loadComponents(config.server, config.token);
var components = await this.sonarService.loadProjects(server, token, projects);

for (let c = 0; c < components.length; c++) {
const component = components[c];
var branches = (await this.sonarService.loadBranches(config.server, config.token, component.key)).filter(b => b.analysisDate);
var branches = (await this.sonarService.loadBranches(server, token, component.key)).filter(b => b.analysisDate);

for (let b = 0; b < branches.length; b++) {
const branch = branches[b];

if (b === 0)
selectedTabs.push(component.key + "_" + branch.name)

branch.measures = await this.sonarService.loadMeasures(config.server, config.token, component.key, branch.name);
branch.measures = await this.sonarService.loadMeasures(server, token, component.key, branch.name);
branch.link = server + "/dashboard?branch=" + branch.name + "&id=" + component.key;
}
component.branches = branches;
}
Expand Down Expand Up @@ -109,7 +120,7 @@ class Code extends React.Component<{}, ICodeState> {
var selectedTab = this.state.selectedTabs.filter(t => t.startsWith(component.key))[0]
if (selectedTab) {
var branchName = selectedTab.replace(component.key + "_", "");
return component.branches.filter(b => b.name == branchName)[0];
return component.branches.filter(b => b.name === branchName)[0];
}
return component.branches[0];
};
Expand Down Expand Up @@ -161,24 +172,32 @@ class Code extends React.Component<{}, ICodeState> {
<Spinner label="loading" />
</div>}

{components.map((component, index) => (
{components.map((component, index) => {

<div>
var currentBranch = this.getCurrentBranch(component);

return (<div>

<CustomHeader className="bolt-header-with-commandbar code--title">
<HeaderIcon
className="bolt-table-status-icon-large code--status"
iconProps={{
render: (className?: string) => {
return renderBranchStatus(this.getCurrentBranch(component), className);
return renderBranchStatus(currentBranch, className);
}
}}
titleSize={TitleSize.Large}
/>
<HeaderTitleArea>
<HeaderTitleRow>
<HeaderTitle ariaLevel={3} className="text-ellipsis" titleSize={TitleSize.Large}>
{component.name}
<Link
excludeTabStop
target="_blank"
href={currentBranch.link}>
{component.name}
</Link>

</HeaderTitle>
</HeaderTitleRow>
<HeaderDescription>
Expand All @@ -188,7 +207,7 @@ class Code extends React.Component<{}, ICodeState> {
<span style={{ flexShrink: 10000 }}>
Last analysis:
<Duration
startDate={new Date(this.getCurrentBranch(component).analysisDate)}
startDate={new Date(currentBranch.analysisDate)}
endDate={new Date()}
/> ago
</span>
Expand Down Expand Up @@ -231,29 +250,31 @@ class Code extends React.Component<{}, ICodeState> {
return (selectedTabs.filter(t => t.startsWith(component.key + "_" + branch.name)).length > 0 && <Card>
<div className="flex-row" style={{ flexWrap: "wrap", marginTop: "20px" }}>
{branch.measures.sortByProp("metric").map((measure, index) => {
var item = configureMeasure(measure);
var item = configureMeasure(measure, branch.measures);
console.log(item);
return (item != null &&
<div className="flex-column" style={{ minWidth: "120px" }} key={index}>
<div className="body-m primary-text">{item.icon} {item.label}</div>
<div className="body-m primary-text">
<h2 className="code--number">
{item.value}
</h2>
{item.status && <Pill className={"code--tag code--tag--" + item.status}>{item.status}</Pill>}
{item.status && <Pill className={"code--tag code--tag--" + item.status}></Pill>}
</div>
</div>
)
})}
</div>
</Card>)
})}
</div>

</div>)

))}

})}
</div>

<CodePanel show={settingsExpanded} onDismiss={() => { this.setState({ settingsExpanded: false, loading: true }); }} />
<CodePanel show={settingsExpanded} onDismiss={() => { this.loadComponents() }} />
</Page>
);
}
Expand Down
28 changes: 28 additions & 0 deletions front/src/services/sonar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,40 @@ export interface ISonarService extends IService {
loadComponents(token: string, serverUrl: string): Promise<ISonarComponent[]>;
loadBranches(serverUrl: string, token: string, project: string): Promise<ISonarBranch[]>;
loadMeasures(serverUrl: string, token: string, component: string, branch: string): Promise<ISonarMeasure[]>;
loadProjects(serverUrl: string, token: string, projects: string[]): Promise<ISonarComponent[]>;
}

export const SonarServiceId = "SonarService";

export class SonarService implements ISonarService {


async loadProjects(serverUrl: string, token: string, projects: string[]): Promise<ISonarComponent[]> {

let base64 = require('base-64');
var projectsString = projects.join(",");

return new Promise<ISonarComponent[]>((resolve: (results: ISonarComponent[]) => void, reject: (error: any) => void): void => {

fetch(serverUrl + "/api/projects/search?ps=100&projects=" + projectsString, {
method: "GET",
mode: 'cors',
headers: {
"Authorization": "Basic " + base64.encode(token + ":")
}
})
.then(res => res.json())
.then((data: any) => {
resolve(data.components);
})
.catch(function (error) {
reject(error);
});

});

}

async loadComponents(serverUrl: string, token: string): Promise<ISonarComponent[]> {

let base64 = require('base-64');
Expand Down
Loading

0 comments on commit 8590a6e

Please sign in to comment.