Skip to content

Commit a88c33b

Browse files
committed
Add first working draft of the chart extension
1 parent 6451b7f commit a88c33b

File tree

6 files changed

+114
-24
lines changed

6 files changed

+114
-24
lines changed

chart/chart.html

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,20 @@
6969
$title.text(`Configure the widget to get Advanced Security alerts trend information`);
7070
}
7171

72+
// init empty object first
73+
let alertTrendLines = {secretAlertTrend: [], dependencyAlertTrend: [], codeAlertsTrend: []};
7274
try {
7375
// get the trend data for alerts first
74-
const secretAlertTrend = getAlertsTrend(organization, projectName, repoId)
75-
consoleLog('alertTrend: ' + JSON.stringify(secretAlertTrend));
76+
alertTrendLines = await getAlertsTrendLines(organization, projectName, repoId)
77+
consoleLog('Dependencies AlertTrend: ' + JSON.stringify(alertTrendLines.dependencyAlertsTrend));
78+
consoleLog('Code scanning AlertTrend: ' + JSON.stringify(alertTrendLines.codeAlertsTrend));
79+
consoleLog('Secrets AlertTrend: ' + JSON.stringify(alertTrendLines.secretAlertsTrend));
7680
}
7781
catch (err) {
7882
consoleLog(`Error loading the alerts trend: ${err}`);
7983
}
84+
85+
const datePoints = getDatePoints();
8086
var chartOptions = {
8187
"hostOptions": {
8288
"height": "290",
@@ -86,18 +92,22 @@
8692
"series":
8793
[
8894
{
89-
"name": "Design",
90-
"data": [1,3,4,3,6,1,9,0,8,11]
95+
"name": "Dependencies",
96+
"data": alertTrendLines.dependencyAlertsTrend
9197
},
9298
{
93-
"name": "On Deck",
94-
"data": [2,4,5,6,7,8,9,10,11,12]
99+
"name": "Code scanning",
100+
"data": alertTrendLines.codeAlertsTrend
95101
},
96102
{
97-
"name": "secrets",
98-
"data": secretAlertTrend
103+
"name": "Secrets",
104+
"data": alertTrendLines.secretAlertsTrend
99105
}
100106
],
107+
"xAxis": {
108+
"labelValues": datePoints,
109+
"labelFormatMode": "dateTime", // format is 2023-09-17
110+
},
101111
"specializedOptions": {
102112
"includeMarkers": "true"
103113
}

img/example_chart_2x2.png

73.3 KB
Loading

library.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ function authenticatedGet(url) {
2727
.then(x => x.json());
2828
}
2929

30-
async function getAlerts (organization, projectName, repoId) {
30+
async function getAlerts(organization, projectName, repoId) {
3131
consoleLog('getAlerts');
3232

3333
try{
34-
// todo: add pagination
34+
// no pagination option, so just get the first 5000 alerts
3535
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`;
3636
consoleLog(`Calling url: [${url}]`);
3737
const alertResult = await authenticatedGet(url);
@@ -53,31 +53,56 @@ async function getAlerts (organization, projectName, repoId) {
5353
}
5454
}
5555

56-
57-
async function getAlertsTrend (organization, projectName, repoId) {
56+
async function getAlertsTrendLines(organization, projectName, repoId) {
5857
consoleLog(`getAlertsTrend for organization [${organization}], project [${projectName}], repo [${repoId}]`);
5958

60-
try{
61-
// todo: add pagination
59+
try {
6260
url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/AdvancedSecurity/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=truen&api-version=7.2-preview.1`;
6361
consoleLog(`Calling url: [${url}]`);
6462
const alertResult = await authenticatedGet(url);
6563
//consoleLog('alertResult: ' + JSON.stringify(alertResult));
6664
consoleLog('alertResult count: ' + alertResult.count);
6765

68-
// load the secret alerts and create a trend line over the last 3 weeks
66+
// load the Secret alerts and create a trend line over the last 3 weeks
6967
const secretAlerts = alertResult.value.filter(alert => alert.alertType === "secret");
70-
const secretAlertsTrend = getAlertsTrendLine(secretAlerts);
68+
const secretAlertsTrend = getAlertsTrendLine(secretAlerts, 'secret');
69+
console.log('');
70+
// load the Dependency alerts and create a trend line over the last 3 weeks
71+
const dependencyAlerts = alertResult.value.filter(alert => alert.alertType === "dependency");
72+
const dependencyAlertsTrend = getAlertsTrendLine(dependencyAlerts, 'dependency');console.log('');
73+
console.log('');
74+
// load the Code alerts and create a trend line over the last 3 weeks
75+
const codeAlerts = alertResult.value.filter(alert => alert.alertType === "code");
76+
const codeAlertsTrend = getAlertsTrendLine(codeAlerts, 'code');
7177

72-
return secretAlertsTrend;
78+
return {
79+
secretAlertsTrend: secretAlertsTrend,
80+
dependencyAlertsTrend: dependencyAlertsTrend,
81+
codeAlertsTrend: codeAlertsTrend
82+
};
7383
}
7484
catch (err) {
7585
consoleLog('error in calling the advec api: ' + err);
7686
}
7787
}
7888

79-
function getAlertsTrendLine(alerts) {
80-
consoleLog('getAlertsTrendLine');
89+
function checkAlertActiveOnDate(alert, dateStr) {
90+
// check if the alert.firstSeenDate is within the date range
91+
// and if fixedDate is not set or is after the date range
92+
const seenClosed = (alert.firstSeenDate.split('T')[0] <= dateStr && (!alert.fixedDate || alert.fixedDate.split('T')[0] > dateStr));
93+
if (seenClosed) {
94+
// check the dismissal.requestedOn date as well
95+
if (alert.dismissal && alert.dismissal.requestedOn) {
96+
const dismissed = (alert.dismissal.requestedOn.split('T')[0] <= dateStr);
97+
return !dismissed;
98+
}
99+
}
100+
101+
return seenClosed;
102+
}
103+
104+
function getAlertsTrendLine(alerts, type) {
105+
consoleLog(`getAlertsTrendLine for type ${type}`);
81106

82107
const trendLine = [];
83108
const trendLineSimple = [];
@@ -88,7 +113,9 @@ function getAlertsTrendLine(alerts) {
88113
for (let d = threeWeeksAgo; d <= today; d.setDate(d.getDate() + 1)) {
89114
const date = new Date(d);
90115
const dateStr = date.toISOString().split('T')[0];
91-
const alertsOnDate = alerts.filter(alert => alert.createdDate.split('T')[0] === dateStr);
116+
117+
const alertsOnDate = alerts.filter(alert => checkAlertActiveOnDate(alert, dateStr));
118+
console.log(`On [${dateStr}] there were [${alertsOnDate.length}] active ${type} alerts`);
92119
trendLine.push({
93120
date: dateStr,
94121
count: alertsOnDate.length
@@ -97,7 +124,21 @@ function getAlertsTrendLine(alerts) {
97124
trendLineSimple.push(alertsOnDate.length);
98125
}
99126

100-
consoleLog('trendLine: ' + JSON.stringify(trendLine));
101-
return trendLine;
127+
consoleLog('trendLine: ' + JSON.stringify(trendLineSimple));
128+
return trendLineSimple;
129+
}
130+
131+
function getDatePoints() {
132+
const trendDates = [];
133+
const today = new Date();
134+
const threeWeeksAgo = new Date();
135+
threeWeeksAgo.setDate(today.getDate() - 21);
136+
137+
for (let d = threeWeeksAgo; d <= today; d.setDate(d.getDate() + 1)) {
138+
const date = new Date(d);
139+
const dateStr = date.toISOString().split('T')[0];
140+
trendDates.push(dateStr);
141+
}
102142

143+
return trendDates;
103144
}

overview.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Install from the marketplace: https://marketplace.visualstudio.com/items?itemNam
1010
### Split it into three separate widgets (1 by 1) with just the single value you scan for:
1111
![Screenshot of the widget in 1 by 1 showing the repository name and the alert count for dependencies, secrets, and code scanning](/img/example_1x1.png)
1212

13+
### Show a trend line (2 by 2) of all alerts in the last 3 weeks:
14+
![Screenshot of the chart widget in 2 by 2 showing the repository name and the alert count for dependencies, secrets, and code scanning](/img/example_chart_2x2.png)
15+
1316
## GitHub repo
1417
Please report issues, feature request, and feedback here: https://github.com/rajbos/GHAzDo-widget.
1518

vss-extension-dev.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifestVersion": 1,
33
"id": "GHAzDoWidget-DEV",
4-
"version": "0.0.1.49",
4+
"version": "0.0.1.66",
55
"public": false,
66
"name": "Advanced Security dashboard Widgets [DEV]",
77
"description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets",

vss-extension.json

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifestVersion": 1,
33
"id": "GHAzDoWidget",
4-
"version": "0.0.1.4",
4+
"version": "0.0.1.5",
55
"public": true,
66
"name": "Advanced Security dashboard Widgets",
77
"description": "GitHub Advanced Security for Azure DevOps dashboard widgets",
@@ -104,6 +104,39 @@
104104
"description": "Configures GHAzDoWidget",
105105
"uri": "widget_1x1/configuration_1x1.html"
106106
}
107+
},
108+
{
109+
"id": "GHAzDoWidget.Chart",
110+
"type": "ms.vss-dashboards-web.widget",
111+
"targets": [
112+
"ms.vss-dashboards-web.widget-catalog",
113+
"RobBos.GHAzDoWidget-DEV.GHAzDoWidget.Chart.Configuration"
114+
],
115+
"properties": {
116+
"name": "GHAzDoWidget - Chart",
117+
"description": "A trend chart widget for Advanced Security alerts.",
118+
"catalogIconUrl": "img/publogo.png",
119+
"uri": "/chart/chart.html",
120+
"supportedSizes": [
121+
{
122+
"rowSpan": 2,
123+
"columnSpan": 2
124+
}
125+
],
126+
"supportedScopes": [
127+
"project_team"
128+
]
129+
}
130+
},
131+
{
132+
"id": "GHAzDoWidget.Chart.Configuration",
133+
"type": "ms.vss-dashboards-web.widget-configuration",
134+
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
135+
"properties": {
136+
"name": "GHAzDoWidget Chart Configuration",
137+
"description": "Configures GHAzDoWidget.Chart",
138+
"uri": "chart/configuration_2x2.html"
139+
}
107140
}
108141
],
109142
"files": [
@@ -113,6 +146,9 @@
113146
{
114147
"path": "widget_2x1", "addressable": true
115148
},
149+
{
150+
"path": "chart", "addressable": true
151+
},
116152
{
117153
"path": "library.js", "addressable": true
118154
},

0 commit comments

Comments
 (0)