Skip to content

Commit

Permalink
First draft of pie chart showing ALL alerts by severity
Browse files Browse the repository at this point in the history
  • Loading branch information
rajbos committed Sep 18, 2023
1 parent 75a4dbb commit d110b26
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 63 deletions.
59 changes: 47 additions & 12 deletions chart/chart.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@
function (WidgetHelpers, Services, context) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("GHAzDoWidget.Chart", function () {
async function renderTrendLine(organization, projectName, repoId, $container, chartService) {
consoleLog('renderTrendLine');
try {
// get the trend data for alerts first
const alertTrendLines = await getAlertsTrendLines(organization, projectName, repoId)
consoleLog('Dependencies AlertTrend: ' + JSON.stringify(alertTrendLines.dependencyAlertsTrend));
consoleLog('Code scanning AlertTrend: ' + JSON.stringify(alertTrendLines.codeAlertsTrend));
consoleLog('Secrets AlertTrend: ' + JSON.stringify(alertTrendLines.secretAlertsTrend));

createChart($container, chartService, alertTrendLines);
}
catch (err) {
consoleLog(`Error loading the alerts trend: ${err}`);
}
}

async function renderPieChart(organization, projectName, repoId, $container, chartService) {
consoleLog('renderPieChart');
try {
// get the trend data for alerts first
const alertSeverityCount = await getAlertSeverityCounts(organization, projectName, repoId);

createPieChart($container, chartService, alertSeverityCount);
}
catch (err) {
consoleLog(`Error loading the alerts trend: ${err}`);
}
}

return {
load: async function(widgetSettings) {
return Services.ChartsService.getService().then(async function(chartService){
Expand Down Expand Up @@ -49,25 +78,31 @@
let repoId
// init empty object first
let alertTrendLines = {secretAlertTrend: [], dependencyAlertTrend: [], codeAlertsTrend: []};
let chartType = 1;
if (data && data.chartType && data.chartType !== "") {
chartType = data.chartType;
consoleLog('loaded chartType from widgetSettings: ' + chartType);
}
else {
consoleLog('chartType is not set, using default value: ' + chartType);
}

if (data && data.repo && data.repo !== "") {
repoName = data.repo;
repoId = data.repoId;
consoleLog('loaded repoName from widgetSettings: ' + repoName);

$title.text(`Advanced Security Alerts Trend`)
$container.text(`${data.repo}`)

try {
// get the trend data for alerts first
alertTrendLines = await getAlertsTrendLines(organization, projectName, repoId)
consoleLog('Dependencies AlertTrend: ' + JSON.stringify(alertTrendLines.dependencyAlertsTrend));
consoleLog('Code scanning AlertTrend: ' + JSON.stringify(alertTrendLines.codeAlertsTrend));
consoleLog('Secrets AlertTrend: ' + JSON.stringify(alertTrendLines.secretAlertsTrend));

createChart($container, chartService, alertTrendLines);
}
catch (err) {
consoleLog(`Error loading the alerts trend: ${err}`);
switch (chartType) {
case "2":
$title.text(`Advanced Security Alerts by Severity`)
renderPieChart(organization, projectName, repoId, $container, chartService);
break;
default:
$title.text(`Advanced Security Alerts Trend`)
renderTrendLine(organization, projectName, repoId, $container, chartService);
break;
}
}
else {
Expand Down
39 changes: 38 additions & 1 deletion chart/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,43 @@ async function createChart($container, chartService, alertTrendLines){
chartService.createChart($container, chartOptions);
}
catch (err) {
console.log(`Error creating chart: ${err}`);
console.log(`Error creating line chart: ${err}`);
}
}

async function createPieChart($container, chartService, alertSeverityCount) {
// convert alertSeverityCount to two arrays, one for the labels and one for the data
consoleLog(`createPieChart for alertSeverityCount: ${JSON.stringify(alertSeverityCount)}`);
const labels = [];
const data = [];
for (const index in alertSeverityCount) {
const item = alertSeverityCount[index];
labels.push(item.severity);
data.push(item.count);
}

var chartOptions = {
"hostOptions": {
"height": "290",
"width": "300"
},
"chartType": "pie",
"series": [{
"data": data
}],
"xAxis": {
"labelValues": labels
},
"specializedOptions": {
"showLabels": "true",
"size": 200
}
};

try {
chartService.createChart($container, chartOptions);
}
catch (err) {
console.log(`Error creating pie chart: ${err}`);
}
}
107 changes: 60 additions & 47 deletions chart/configuration_2x2.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="../lib/VSS.SDK.min.js"></script>
<script src="../library.js"></script>
<link rel="stylesheet" href="../styles.css" />

<script type="text/javascript">
Expand All @@ -14,30 +15,41 @@
function (Service, WidgetHelpers, context, GitWebApi) {
VSS.register("GHAzDoWidget.Chart.Configuration", function () {
var $repoDropdown = $("#repo-dropdown");
var $chartTypeDropdown = $("#chart-type");

async function getRepos() {
try {
const webContext = VSS.getWebContext();
const project = webContext.project;
function reloadWidget(widgetConfigurationContext) {
const customSettings = getSettings();
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
}

// todo: load the available repos in this project
const gitClient = Service.getClient(GitWebApi.GitHttpClient);
repos = await gitClient.getRepositories(project.name);
console.log(`Found these repos: ${JSON.stringify(repos)}`);
return repos;
}
catch (err) {
console.log(`Error loading the available repos: ${err}`);
return [];
function getSettings() {
if (repos) {
// find the repo with this name
const repo = repos.find(r => r.name === $repoDropdown.val());

if (repo) {
var customSettings = {
data: JSON.stringify({
repo: $repoDropdown.val(),
repoId: repo.id,
chartType: $chartTypeDropdown.val()
})
};
return customSettings;
}
}

return {};
}

return {
load: async function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
console.log(`Loading the Chart.2x2 settings with ${JSON.stringify(settings)}`)

const repos = await getRepos();
const repos = await getRepos(VSS, Service, GitWebApi);
// add all repos as selection options to the dropdown
if (repos) {
// add a top option to select no repo
Expand All @@ -54,39 +66,25 @@
$repoDropdown.val(settings.repo);
}

$repoDropdown.on("change", function () {
let repo;
if (repos) {
// find the repo with this name
repo = repos.find(r => r.name === $repoDropdown.val());
}
if (settings && settings.chartType) {
// select the repo that was saved in the settings
$chartTypeDropdown.val(settings.chartType);
}

// register a change event handler for the dropdowns
$repoDropdown.on("change", function () {
reloadWidget(widgetConfigurationContext)
});

var customSettings = {
data: JSON.stringify({
repo: $repoDropdown.val(),
repoId: repo.id
})
};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
$chartTypeDropdown.on("change", function () {
reloadWidget(widgetConfigurationContext)
});

return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: async function() {
const repos = await getRepos();
let repo;
if (repos) {
// find the repo with this name
repo = repos.find(r => r.name === $repoDropdown.val());
}
var customSettings = {
data: JSON.stringify({
repo: $repoDropdown.val(),
repoId: repo.id
})
};
const customSettings = getSettings();

console.log(`Saving the Chart.2x2 settings with ${JSON.stringify(customSettings)}`)
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
Expand All @@ -98,12 +96,27 @@
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Repository: </label>
<select id="repo-dropdown" style="margin-top:10px">
<!-- todo: dynamically load the available repos in this project-->
</select>
</fieldset>
<table class="ghazdo-table">
<tr>
<td>
<label class="label">Repository: </label>
</td>
<td>
<select id="repo-dropdown" class="dropdown" style="margin-top:10px"></select>
</td>
</tr>
<tr>
<td>
<label class="label">Chart type: </label>
</td>
<td>
<select id="chart-type" class="dropdown">
<option value="1">Trend line</option>
<option value="2">Pie chart</option>
</select>
</td>
</tr>
</table>
</div>
</body>
</html>
42 changes: 40 additions & 2 deletions library.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function getAlerts(organization, projectName, repoId) {

try{
// no pagination option, so just get the first 5000 alerts
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=truen&criteria.states=1&api-version=7.2-preview.1`;
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=7.2-preview.1`;
consoleLog(`Calling url: [${url}]`);
const alertResult = await authenticatedGet(url);
//consoleLog('alertResult: ' + JSON.stringify(alertResult));
Expand All @@ -57,7 +57,7 @@ async function getAlertsTrendLines(organization, projectName, repoId) {
consoleLog(`getAlertsTrend for organization [${organization}], project [${projectName}], repo [${repoId}]`);

try {
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=truen&api-version=7.2-preview.1`;
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&api-version=7.2-preview.1`;
consoleLog(`Calling url: [${url}]`);
const alertResult = await authenticatedGet(url);
//consoleLog('alertResult: ' + JSON.stringify(alertResult));
Expand Down Expand Up @@ -162,4 +162,42 @@ async function getRepos(VSS, Service, GitWebApi) {
console.log(`Error loading the available repos: ${err}`);
return [];
}
}

async function getAlertSeverityCounts(organization, projectName, repoId) {
try {
// todo: filter on alertType
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.states=1&api-version=7.2-preview.1`;
consoleLog(`Calling url: [${url}]`);
const alertResult = await authenticatedGet(url);
//consoleLog('alertResult: ' + JSON.stringify(alertResult));
consoleLog('total alertResult count: ' + alertResult.count);

// group the alerts based on the severity
let severityClasses = [
{ severity: "critical", count: 0 },
{ severity: "high", count: 0 },
{ severity: "medium", count: 0},
{ severity: "low", count: 0}
];
try {
consoleLog(`severityClasses.length: [${severityClasses.length}]`);

for (let index in severityClasses) {
let severityClass = severityClasses[index];
const severityAlertCount = alertResult.value.filter(alert => alert.severity === severityClass.severity);
consoleLog(`severityClass [${severityClass.severity}] has [${severityAlertCount.length}] alerts`);
severityClass.count = severityAlertCount.length;
};
}
catch (err) {
consoleLog('error in grouping the alerts: ' + err);
}

consoleLog('severityClasses summarized: ' + JSON.stringify(severityClasses));
return severityClasses;
}
catch (err) {
consoleLog('error in calling the advec api: ' + err);
}
}
11 changes: 11 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@
.alertValue {
margin-block-start: 10px;
}

.ghazdo-table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #dddddd00;
}

.ghazdo-table tr {
margin-top: 6px;
}
2 changes: 1 addition & 1 deletion vss-extension-dev.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifestVersion": 1,
"id": "GHAzDoWidget-DEV",
"version": "0.0.1.84",
"version": "0.0.1.96",
"public": false,
"name": "Advanced Security dashboard Widgets [DEV]",
"description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets",
Expand Down
1 change: 1 addition & 0 deletions widget_1x1/configuration_1x1.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="../lib/VSS.SDK.min.js"></script>
<script src="../library.js"></script>
<link rel="stylesheet" href="../styles.css" />

<script type="text/javascript">
Expand Down

0 comments on commit d110b26

Please sign in to comment.