From 2ba035fe645c3e71cd78a21857a8b5c3a209e272 Mon Sep 17 00:00:00 2001 From: RoderickB <13252390+webtonize@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:22:43 +0100 Subject: [PATCH] Add trends (#28) --- src/bicep/modules/azdo-main.bicep | 145 ++++++++++++++---- src/bicep/modules/azdo-resource-state.bicep | 6 +- .../modules/azdo-resources-by-rule.bicep | 41 ++++- 3 files changed, 162 insertions(+), 30 deletions(-) diff --git a/src/bicep/modules/azdo-main.bicep b/src/bicep/modules/azdo-main.bicep index 47fe86d..47dae67 100644 --- a/src/bicep/modules/azdo-main.bicep +++ b/src/bicep/modules/azdo-main.bicep @@ -78,7 +78,7 @@ var workbook = { queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' value: [ - 'value::all' + 'psrule-scan-ado' ] } ] @@ -92,7 +92,7 @@ var workbook = { type: 3 content: { version: 'KqlItem/1.0' - query: 'PSRule_CL\r\n| sort by TimeGenerated asc \r\n| extend \r\n a=parse_json(Annotations_s),\r\n f=parse_json(Field_s)\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| summarize \r\n [\'Audit DateTime\']=max(TimeGenerated),\r\n [\'Failed Checkpoints\']=countif(Outcome_s == \'Fail\'),\r\n [\'Passed Checkpoints\']=countif(Outcome_s == \'Pass\'),\r\n [\'Rules Checked\']=dcount(RuleName_s),\r\n [\'Resources Checked\']=dcount(TargetName_s)\r\n by \r\n RunId_s\r\n//| project RunId_s, todatetime([\'Audit DateTime\']), [\'Rules Checked\'], [\'Resources Checked\'], [\'Failed Checkpoints\'], [\'Passed Checkpoints\']\r\n| sort by [\'Audit DateTime\'] desc\r\n| top 4 by [\'Audit DateTime\'] desc' + query: 'PSRule_CL\r\n| sort by TimeGenerated asc \r\n| extend \r\n a=parse_json(Annotations_s),\r\n f=parse_json(Field_s)\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| summarize \r\n [\'Audit DateTime\']=format_datetime(max(TimeGenerated), "yyyy-MM-dd HH:mm"),\r\n [\'Failed Checkpoints\']=countif(Outcome_s == \'Fail\'),\r\n [\'Passed Checkpoints\']=countif(Outcome_s == \'Pass\'),\r\n [\'Total Checkpoints\']=count(Outcome_s),\r\n [\'Rules Checked\']=dcount(RuleName_s),\r\n [\'Resources Checked\']=dcount(TargetName_s)\r\n by \r\n RunId_s\r\n//| project RunId_s, todatetime([\'Audit DateTime\']), [\'Rules Checked\'], [\'Resources Checked\'], [\'Failed Checkpoints\'], [\'Passed Checkpoints\']\r\n| sort by [\'Audit DateTime\'] desc\r\n| extend\r\n [\'Pass Percentage\']=round(100 * todecimal([\'Passed Checkpoints\']) / todecimal([\'Total Checkpoints\']), 2)\r\n| extend\r\n [\'Trend\']=round([\'Pass Percentage\'] - next([\'Pass Percentage\']), 2) \r\n| top 4 by [\'Audit DateTime\'] desc' size: 4 timeContext: { durationMs: 2592000000 @@ -103,8 +103,9 @@ var workbook = { tileSettings: { titleContent: { columnMatch: 'Audit DateTime' - formatter: 6 + formatter: 1 dateFormat: { + showUtcTime: null formatName: 'shortDateTimePattern' } tooltipFormat: { @@ -117,50 +118,79 @@ var workbook = { tooltip: 'Resources checked' } } - leftContent: { - columnMatch: 'Failed Checkpoints' - formatter: 12 - formatOptions: { - palette: 'red' - } - tooltipFormat: { - tooltip: 'Failed checkpoints' - } - } rightContent: { - columnMatch: 'Passed Checkpoints' - formatter: 12 + columnMatch: 'Trend' + formatter: 18 formatOptions: { - palette: 'green' + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: '0' + representation: 'Subtract' + text: '{0}{1}' + } + { + operator: '<' + thresholdValue: '0' + representation: 'trenddown' + text: '{0}{1}' + } + { + operator: '>' + thresholdValue: '0' + representation: 'trendup' + text: '{0}{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'success' + text: '{0}{1}' + } + ] compositeBarSettings: { labelText: '' columnSettings: [] } } + numberFormat: { + unit: 1 + options: { + style: 'decimal' + minimumFractionDigits: 2 + } + } tooltipFormat: { - tooltip: 'Passed checkpoints' + tooltip: 'Pass trend' } } secondaryContent: { - columnMatch: 'Rules Checked' - formatter: 2 + columnMatch: 'Pass Percentage' + formatter: 22 formatOptions: { compositeBarSettings: { - labelText: '' + labelText: 'Pass percentage ["Pass Percentage"]%' columnSettings: [ - { - columnName: 'Failed Checkpoints' - color: 'redBright' - } { columnName: 'Passed Checkpoints' color: 'green' } + { + columnName: 'Failed Checkpoints' + color: 'redBright' + } ] } } + numberFormat: { + unit: 1 + options: { + style: 'decimal' + } + } tooltipFormat: { - tooltip: 'Rules checked' + tooltip: 'Pass Percentage' } } showBorder: true @@ -273,13 +303,14 @@ var workbook = { type: 3 content: { version: 'KqlItem/1.0' - query: 'PSRule_CL\r\n| sort by TimeGenerated asc \r\n| extend \r\n a=parse_json(Annotations_s),\r\n f=parse_json(Field_s)\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| summarize \r\n [\'Audit DateTime\']=max(TimeGenerated),\r\n [\'Failed Checkpoints\']=countif(Outcome_s == \'Fail\'),\r\n [\'Passed Checkpoints\']=countif(Outcome_s == \'Pass\'),\r\n [\'Rules Checked\']=dcount(RuleName_s),\r\n [\'Resources Checked\']=dcount(TargetName_s)\r\n by \r\n RunId_s\r\n| project RunId_s, todatetime([\'Audit DateTime\']), [\'Rules Checked\'], [\'Resources Checked\'], [\'Failed Checkpoints\'], [\'Passed Checkpoints\']\r\n| sort by [\'Audit DateTime\'] desc\r\n' + query: 'PSRule_CL\r\n| sort by TimeGenerated asc \r\n| extend \r\n a=parse_json(Annotations_s),\r\n f=parse_json(Field_s)\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| summarize \r\n [\'Audit DateTime\']=max(TimeGenerated),\r\n [\'Failed Checkpoints\']=countif(Outcome_s == \'Fail\'),\r\n [\'Passed Checkpoints\']=countif(Outcome_s == \'Pass\'),\r\n [\'Total Checkpoints\']=count(Outcome_s),\r\n [\'Rules Checked\']=dcount(RuleName_s),\r\n [\'Resources Checked\']=dcount(TargetName_s)\r\n by \r\n RunId_s\r\n| extend\r\n [\'Pass Percentage\']=round(100 * todecimal([\'Passed Checkpoints\']) / todecimal([\'Total Checkpoints\']), 2)\r\n| project RunId_s, todatetime([\'Audit DateTime\']), [\'Rules Checked\'], [\'Resources Checked\'], [\'Failed Checkpoints\'], [\'Passed Checkpoints\'], [\'Pass Percentage\']\r\n| sort by [\'Audit DateTime\'] desc\r\n| extend\r\n [\'Trend\']=round([\'Pass Percentage\'] - next([\'Pass Percentage\']), 2)' size: 0 timeContext: { durationMs: 2592000000 } queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' + visualization: 'table' gridSettings: { formatters: [ { @@ -328,6 +359,12 @@ var workbook = { viewerMode: false } } + numberFormat: { + unit: 17 + options: { + style: 'decimal' + } + } } { columnMatch: 'Resources Checked' @@ -364,6 +401,12 @@ var workbook = { viewerMode: false } } + numberFormat: { + unit: 17 + options: { + style: 'decimal' + } + } } { columnMatch: 'Failed Checkpoints' @@ -379,6 +422,56 @@ var workbook = { aggregation: 'Sum' } } + { + columnMatch: 'Pass Percentage' + formatter: 1 + numberFormat: { + unit: 1 + options: { + style: 'decimal' + } + } + } + { + columnMatch: 'Trend' + formatter: 18 + formatOptions: { + thresholdsOptions: 'icons' + thresholdsGrid: [ + { + operator: '==' + thresholdValue: '0' + representation: 'Subtract' + text: '{0}{1}' + } + { + operator: '<' + thresholdValue: '0' + representation: 'trenddown' + text: '{0}{1}' + } + { + operator: '>' + thresholdValue: '0' + representation: 'trendup' + text: '{0}{1}' + } + { + operator: 'Default' + thresholdValue: null + representation: 'success' + text: '{0}{1}' + } + ] + } + numberFormat: { + unit: 1 + options: { + style: 'decimal' + minimumFractionDigits: 2 + } + } + } ] sortBy: [ { diff --git a/src/bicep/modules/azdo-resource-state.bicep b/src/bicep/modules/azdo-resource-state.bicep index e985dae..b9af6d9 100644 --- a/src/bicep/modules/azdo-resource-state.bicep +++ b/src/bicep/modules/azdo-resource-state.bicep @@ -38,7 +38,7 @@ var workbook = { } queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' - value: 'c442098b2437a6f6f3496768ba2f698790ecab2f' + value: 'psrule-scan-ado/287' } { id: 'ae4e2baa-2cc1-4dc6-a31b-0b0ca2dcf2c1' @@ -93,7 +93,7 @@ var workbook = { queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' value: [ - 'value::all' + 'psrule-scan-ado' ] } ] @@ -223,7 +223,7 @@ var workbook = { type: 3 content: { version: 'KqlItem/1.0' - query: 'PSRule_CL\r\n| where RunId_s == \'{runId:value}\'\r\n| extend a=parse_json(Annotations_s), f=parse_json(Field_s)\r\n| extend Severity=a.severity, [\'Rule Help Url\']=a.[\'online version\'],Category=a.category\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| extend severity_level = case(\r\n Severity == "Informational" and Outcome_s == \'Fail\', 1,\r\n Severity == "Important" and Outcome_s == \'Fail\', 2,\r\n Severity == "Severe" and Outcome_s == \'Fail\', 3,\r\n Severity == "Critical" and Outcome_s == \'Fail\', 4,\r\n 0)\r\n| summarize\r\n [\'Resource state\'] = arg_max(severity_level, *),\r\n [\'Failed Rules\'] = countif(Outcome_s == \'Fail\'),\r\n [\'Passed Rules\'] = countif(Outcome_s == \'Pass\')\r\n by tostring(TargetName_s)\r\n| extend Findings = case(\r\n [\'Resource state\'] == 1, "Informational",\r\n [\'Resource state\'] == 2, "Important",\r\n [\'Resource state\'] == 3, "Severe",\r\n [\'Resource state\'] == 4, "Critical",\r\n [\'Resource state\'] == 0, "Passed all rules",\r\n "Not found")\r\n| project Organization, Project, [\'Resource Name\']=ResourceName, [\'Resource Type\'] = TargetType_s, Findings, [\'Failed Rules\'], [\'Passed Rules\'], [\'Resource FQN\'] = TargetName_s\r\n| where Findings == \'{SeverityFilter}\' or \'All Resources\' == \'{SeverityFilter}\'\r\n' + query: 'PSRule_CL\r\n| where RunId_s == \'{runId:value}\'\r\n| extend a=parse_json(Annotations_s), f=parse_json(Field_s)\r\n| extend Severity=a.severity, [\'Rule Help Url\']=a.[\'online version\'],Category=a.category\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| extend severity_level = case(\r\n Severity == "Informational" and Outcome_s == \'Fail\', 1,\r\n Severity == "Important" and Outcome_s == \'Fail\', 2,\r\n Severity == "Severe" and Outcome_s == \'Fail\', 3,\r\n Severity == "Critical" and Outcome_s == \'Fail\', 4,\r\n 0)\r\n| summarize\r\n [\'Resource state\'] = arg_max(severity_level, *),\r\n [\'Failed Rules\'] = countif(Outcome_s == \'Fail\'),\r\n [\'Passed Rules\'] = countif(Outcome_s == \'Pass\')\r\n by tostring(TargetName_s)\r\n| sort by [\'Resource state\'] desc\r\n| extend Findings = case(\r\n [\'Resource state\'] == 1, "Informational",\r\n [\'Resource state\'] == 2, "Important",\r\n [\'Resource state\'] == 3, "Severe",\r\n [\'Resource state\'] == 4, "Critical",\r\n [\'Resource state\'] == 0, "Passed all rules",\r\n "Not found")\r\n| project Organization, Project, [\'Resource Name\']=ResourceName, [\'Resource Type\'] = TargetType_s, Findings, [\'Failed Rules\'], [\'Passed Rules\'], [\'Resource FQN\'] = TargetName_s\r\n| where Findings == \'{SeverityFilter}\' or \'All Resources\' == \'{SeverityFilter}\'\r\n' size: 0 timeContext: { durationMs: 2592000000 diff --git a/src/bicep/modules/azdo-resources-by-rule.bicep b/src/bicep/modules/azdo-resources-by-rule.bicep index db30e93..b0c1154 100644 --- a/src/bicep/modules/azdo-resources-by-rule.bicep +++ b/src/bicep/modules/azdo-resources-by-rule.bicep @@ -41,6 +41,9 @@ var workbook = { defaultValue: 'value::all' queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' + value: [ + 'Azure.DevOps.Tasks.VariableGroup.Description' + ] } { id: '69ed445b-75d8-41bd-bd99-ed5a99a0cc3a' @@ -60,7 +63,7 @@ var workbook = { } queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' - value: 'daf923fb2463077368e92134a02eda43787659ec' + value: 'psrule-scan-ado/288' } { id: '01f572fc-067d-4429-9be7-9299a08122fd' @@ -110,6 +113,9 @@ var workbook = { defaultValue: 'value::all' queryType: 0 resourceType: 'microsoft.operationalinsights/workspaces' + value: [ + 'value::all' + ] } ] style: 'above' @@ -118,6 +124,39 @@ var workbook = { } name: 'parameters - 0' } + { + type: 3 + content: { + version: 'KqlItem/1.0' + query: 'PSRule_CL\r\n| where DisplayName_s in ({ruleName}) or \'All Rules\' in ({ruleName})\r\n| extend a=parse_json(Annotations_s), f=parse_json(Field_s)\r\n| extend Severity=a.severity, [\'Rule Help Url\']=a.[\'online version\'],Category=a.category\r\n| extend \r\n expandedId=parse_json(tostring(f.id))\r\n| extend \r\n Organization=expandedId.organization,\r\n [\'Project\']=expandedId.[\'project\'],\r\n ResourceName=expandedId.resourceName\r\n| where (Organization in ({Organization}) or \'All Organizations\' in ({Organization})) and (Project in ({Project}) or \'All Projects\' in ({Project})) \r\n| summarize\r\n [\'Passed Resources\']=countif(Outcome_s == \'Pass\'),\r\n [\'Failed Resources\']=countif(Outcome_s == \'Fail\'),\r\n [\'Percentage Passed\']=round(100 * todecimal(countif(Outcome_s == \'Pass\')) / todecimal(count()), 2),\r\n [\'Audit Date Time\']=max(TimeGenerated)\r\n by RunId_s\r\n| project [\'Audit Date Time\'], [\'Percentage Passed\']' + size: 4 + aggregation: 2 + timeContext: { + durationMs: 2592000000 + } + queryType: 0 + resourceType: 'microsoft.operationalinsights/workspaces' + visualization: 'linechart' + chartSettings: { + xAxis: 'Audit Date Time' + yAxis: [ + 'Percentage Passed' + ] + showLegend: true + showDataPoints: true + ySettings: { + numberFormatSettings: { + unit: 1 + options: { + style: 'decimal' + useGrouping: true + } + } + } + } + } + name: 'query - 2' + } { type: 3 content: {