diff --git a/vss-extension-dev.json b/vss-extension-dev.json index beea985..c2c9b74 100644 --- a/vss-extension-dev.json +++ b/vss-extension-dev.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "GHAzDoWidget-DEV", - "version": "0.2.319", + "version": "0.2.330", "public": false, "name": "Advanced Security dashboard Widgets [DEV]", "description": "[DEV] GitHub Advanced Security for Azure DevOps dashboard widgets", diff --git a/widgets/library.js b/widgets/library.js index d96c62f..39b5e37 100644 --- a/widgets/library.js +++ b/widgets/library.js @@ -361,6 +361,10 @@ async function getProjects(VSS, Service, CoreRestClient) { id: project.id } }); + + // sort the projects based on the name + projects.sort((a, b) => (a.name > b.name) ? 1 : -1); + //consoleLog(`Converted projects to: ${JSON.stringify(projects)}`); // save the repos to the document store for next time @@ -378,6 +382,128 @@ async function getProjects(VSS, Service, CoreRestClient) { } } +const batchTimeOut = 1000 +const repoBatchSize = 100 +async function loadAllAlertsFromAllRepos(VSS, Service, GitWebApi, organization, projects, progressDiv) { + consoleLog(`Loading all alerts from all repos for organization [${organization}] for [${projects.length}] projects`); + // prepare the list of project calls to make + for (let index in projects) { + const project = projects[index]; + //$queryinfocontainer.append(`
  • ${project.name}
  • \n`); + + // place the repo definition in the array to process later + getProjectCalls.push({organization, project}); + } + progressDiv.projectCount.textContent = `${projects.length}` + + // wait for all the project promises to complete + console.log(`Waiting for [${getProjectCalls.length}] calls to complete`); + let position = 0; + let waiting = 0; + let alertBatchSize = repoBatchSize; // size of the calls to run at once + activeCalls = 0; // reset the active calls counter + let i = 0 + while (i < getProjectCalls.length) { + if (activeCalls < alertBatchSize) { + let work = getProjectCalls[i]; + + // do the work, don't wait for it to complete to speed things up + getRepos(VSS, Service, GitWebApi, work.project.name, false).then(repos => { + activeCalls--; + showRepoInfo(repos, work.project, work.organization, progressDiv, activeCalls) + }). + catch(error => { + console.error(`Error handling get repos for ${work.project.name}: ${error}`); + activeCalls--; + }) + + activeCalls++; + i++ + } + else { + //showCallStatus(); + // wait for the next batch to complete + await new Promise(resolve => setTimeout(resolve, batchTimeOut)); + } + } + + while (activeCalls > 0) { + // wait for the last batch to complete + console.log(`Waiting for the last [${activeCalls}] project calls to complete`); + await new Promise(resolve => setTimeout(resolve, 500)); + } + console.log(`All project calls completed`); + + // loop over the alertCall array and load the alerts for each repo + position = 0; + waiting = 0; + alertBatchSize = 50; // size of the calls to run at once + activeCalls = 0; + i = 0 + const repos = null // required param, but not used + while (i < getAlertCalls.length) { + if (activeCalls < alertBatchSize) { + let work = getAlertCalls[i]; + // do the work + getAlerts(work.organization, work.project.name, work.repo.id, repos, work.project, work.repo).then(repoAlerts => { + activeCalls--; + showAlertInfo(work.organization, work.project, work.repo, repoAlerts, progressDiv, activeCalls) + //todo: store the overall results: results.push(repoAlerts); + }). + catch(error => { + console.error(`Error handling get alerts for ${work.repo.name}: ${error}`); + activeCalls--; + }) + + activeCalls++; + i++ + } + else + { + showCallStatus(); + // wait some time before starting the next batch + await new Promise(resolve => setTimeout(resolve, 500)); + } + } +} +async function showRepoInfo(repos, project, organization, progressDiv, activeCalls) { + consoleLog(`Found [${repos?.length}] repos for project [${project.name}] to load alerts for`); + + var currentValue = parseInt(progressDiv.repoCount.textContent); + progressDiv.repoCount.textContent = currentValue + repos?.length + + // load the work to load the alerts for all repos + for (let repoIndex in repos) { + const repo = repos[repoIndex]; + // only load the alerts if the repo has a size, otherwise it is still an empty repo + if (repo.size > 0) { + // add work definition to array + getAlertCalls.push({organization, project, repo}); + } + } +} + +function showAlertInfo(organization, project, repo, repoAlerts, progressDiv, activeCalls) { + if (repoAlerts && repoAlerts.values) { + //consoleLog(`Found [${JSON.stringify(repoAlerts)}] dependency alerts for repo [${repo.name}]`) + + // show the alert counts + // todo: store the current count and skip the parsing + var currentValue = parseInt(progressDiv.dependencyAlertCount.textContent) + progressDiv.dependencyAlertCount.textContent = currentValue + repoAlerts.values.dependencyAlerts + + var currentValue = parseInt(progressDiv.secretAlertCount.textContent) + progressDiv.secretAlertCount.textContent = currentValue + repoAlerts.values.secretAlerts + + var currentValue = parseInt(progressDiv.codeAlertCount.textContent) + progressDiv.codeAlertCount.textContent = currentValue + repoAlerts.values.codeAlerts + + // keep track of global state: + // endDateTime.text('End: ' + (new Date()).toISOString()) + // showCallStatus() + } +} + async function getRepos(VSS, Service, GitWebApi, projectName, useCache = true) { //consoleLog(`inside getRepos`); @@ -465,25 +591,28 @@ async function getAlertSeverityCounts(organization, projectName, repoId, alertTy url = `https://advsec.dev.azure.com/${organization}/${projectName}/_apis/${areaName}/repositories/${repoId}/alerts?top=5000&criteria.onlyDefaultBranchAlerts=true&criteria.alertType=${alertType.value}&criteria.states=1&api-version=${apiVersion}`; //consoleLog(`Calling url: [${url}]`); const alertResult = await authenticatedGet(url); - //consoleLog('alertResult: ' + JSON.stringify(alertResult)); - consoleLog(`total alertResult count: ${alertResult.count} for alertType [${alertType.name}]`); - // group the alerts based on the severity - 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); - } + if (alertResult && alertResult.count) { + //consoleLog('alertResult: ' + JSON.stringify(alertResult)); + consoleLog(`total alertResult count: ${alertResult.count} for alertType [${alertType.name}]`); + + // group the alerts based on the severity + 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)); + consoleLog('severityClasses summarized: ' + JSON.stringify(severityClasses)); + } } catch (err) { consoleLog('error in calling the advec api: ' + err); diff --git a/widgets/widgets/hub/hub.html b/widgets/widgets/hub/hub.html index 9bd9782..e12026f 100644 --- a/widgets/widgets/hub/hub.html +++ b/widgets/widgets/hub/hub.html @@ -15,14 +15,17 @@ }); VSS.require([ + "VSS/Service", "TFS/Dashboards/WidgetHelpers", "Charts/Services", "VSS/Context", - "VSS/Authentication/Services" + //"VSS/Authentication/Services", + "TFS/VersionControl/GitRestClient", + "TFS/Core/RestClient" ], // top level function - async function (WidgetHelpers, Services, context) { + async function (Service, WidgetHelpers, Services, context, GitWebApi, RestClient) { consoleLog('VSS.require function'); try { consoleLog('VSS.register Hub function'); @@ -39,10 +42,22 @@ consoleLog('project name: ' + projectName); consoleLog('organization name: ' + organization); - const titleElement = $('h2.ghazdoTitle')[0]; - const containerElement = $('#Chart-Container')[0]; + // load all project data + const progressDivContainer = $('#LoadingInfoContainer')[0]; + const progressDiv = { + projectCount: $(progressDivContainer).find('.projectCount')[0], + repoCount: $(progressDivContainer).find('.repoCount')[0], + dependencyAlertCount: $(progressDivContainer).find('.dependencyAlertCount')[0], + secretAlertCount: $(progressDivContainer).find('.secretAlertCount')[0], + codeAlertCount: $(progressDivContainer).find('.codeAlertCount')[0] + } + const projects = await getProjects(VSS, Service, RestClient); + consoleLog(`Found [${projects?.length}] projects`); + loadAllAlertsFromAllRepos(VSS, Service, GitWebApi, organization, projects, progressDiv) // create the charts + const titleElement = $('h2.ghazdoTitle')[0]; + const containerElement = $('#Chart-Container')[0]; await createCharts({chartService, containerElement, titleElement, projectName, organization}); consoleLog('returning WidgetStatusHelper.Success()'); @@ -55,9 +70,34 @@ ); -
    -

    Loading information div

    -
    +
    +

    Loading information

    +
    +
    +

    Projects

    +

    0

    +
    + +
    +

    Repos

    +

    0

    +
    + +
    +

    Dependencies

    +

    0

    +
    + +
    +

    Secrets

    +

    0

    +
    + +
    +

    Code

    +

    0

    +
    +
    @@ -77,6 +117,10 @@

    GHAzDo Hub test from Rob Bos's widget

    .region-headerBreadcrumb { display: none; } + + #progressDivContainer { + width: 435px; + } \ No newline at end of file diff --git a/widgets/widgets/hub/hub.js b/widgets/widgets/hub/hub.js index b698ab4..46c8945 100644 --- a/widgets/widgets/hub/hub.js +++ b/widgets/widgets/hub/hub.js @@ -69,10 +69,10 @@ async function createHubChart({chartService, containerElement, titleElement, org } break } - consoleLog('rendered chart') + consoleLog('Rendered chart') } else { - consoleLog('configuration is needed first, opening with empty values') + consoleLog('Configuration is needed first, opening with empty values') // set the tile to indicate config is needed titleElement.textContent = `Configure the widget to get Advanced Security alerts trend information` } diff --git a/widgets/widgets/testing_widget/testing.html b/widgets/widgets/testing_widget/testing.html index cd5efcd..e3b7c36 100644 --- a/widgets/widgets/testing_widget/testing.html +++ b/widgets/widgets/testing_widget/testing.html @@ -1,11 +1,6 @@ - - @@ -247,8 +242,6 @@ const projects = await getProjects(VSS, Service, RestClient); consoleLog(`Found [${projects?.length}] projects`); - // sort the projects based on the name - projects.sort((a, b) => (a.name > b.name) ? 1 : -1); showProjectInfo(VSS, Service, GitWebApi, organization, projects) } diff --git a/z-status.txt b/z-status.txt index 5a4c8a8..503ae55 100644 --- a/z-status.txt +++ b/z-status.txt @@ -4,4 +4,10 @@ In this branch was added: - write the loop to go over ALL repos in the org (might be already be available in another branch) -Do check the versions of the widget before pushing! \ No newline at end of file +Do check the versions of the widget before pushing! + + +WIP: + Load all alerts in the hub and show them. + Currently the continue after the project calls does not go into loading the alerts, + for org xpirit, seems to work in raj-bos \ No newline at end of file