From 0030e00935b30982975fc13a93427a397d5b0a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Thu, 7 May 2026 21:14:29 -0300 Subject: [PATCH 01/11] feat: add support for postgresql to grafana dashboards --- .../postgresql/AICostEfficiency.json | 537 +++ grafana/dashboards/postgresql/AIModelROI.json | 206 + grafana/dashboards/postgresql/ArgoCD.json | 1033 +++++ grafana/dashboards/postgresql/Asana.json | 1192 +++++ .../dashboards/postgresql/AzureDevOps.json | 2335 ++++++++++ grafana/dashboards/postgresql/Bamboo.json | 1219 ++++++ grafana/dashboards/postgresql/BitBucket.json | 2335 ++++++++++ grafana/dashboards/postgresql/CircleCI.json | 1272 ++++++ .../ComponentAndFileLevelMetrics.json | 1443 ++++++ .../postgresql/ContributorExperience.json | 930 ++++ grafana/dashboards/postgresql/DORA.json | 1544 +++++++ grafana/dashboards/postgresql/DORAByTeam.json | 1254 ++++++ grafana/dashboards/postgresql/DORADebug.json | 3863 +++++++++++++++++ .../DORADetails-ChangeFailureRate.json | 758 ++++ .../DORADetails-DeploymentFrequency.json | 1335 ++++++ ...ADetails-FailedDeploymentRecoveryTime.json | 478 ++ .../DORADetails-LeadTimeforChanges.json | 967 +++++ .../DORADetails-TimetoRestoreService.json | 465 ++ ...oAverageRequirementLeadTimeByAssignee.json | 250 ++ .../postgresql/DemoCommitCountByAuthor.json | 257 ++ .../postgresql/DemoDetailedBugInfo.json | 315 ++ .../dashboards/postgresql/DemoHomepage.json | 191 + ...FastDoWeRespondToCustomerRequirements.json | 223 + ...DemoIsThisMonthMoreProductiveThanLast.json | 253 ++ .../DemoWasOurQualityImprovedOrNot.json | 220 + .../DeveloperProductivityHours.json | 267 ++ .../postgresql/EngineeringOverview.json | 2386 ++++++++++ .../EngineeringThroughputAndCycleTime.json | 1964 +++++++++ ...neeringThroughputAndCycleTimeTeamView.json | 3419 +++++++++++++++ grafana/dashboards/postgresql/GitHub.json | 3658 ++++++++++++++++ .../postgresql/GithubCopilotAdoption.json | 2322 ++++++++++ .../GithubCopilotDORACorrelation.json | 2931 +++++++++++++ ...ReleaseQualityAndContributionAnalysis.json | 2712 ++++++++++++ grafana/dashboards/postgresql/Gitlab.json | 2327 ++++++++++ grafana/dashboards/postgresql/Homepage.json | 310 ++ grafana/dashboards/postgresql/Jenkins.json | 1202 +++++ grafana/dashboards/postgresql/Jira.json | 1248 ++++++ .../postgresql/KiroCreditsDORA.json | 709 +++ .../postgresql/LanguageAIHeatmap.json | 276 ++ .../postgresql/MultiAIComparison.json | 464 ++ grafana/dashboards/postgresql/Opsgenie.json | 1536 +++++++ grafana/dashboards/postgresql/PagerDuty.json | 1295 ++++++ grafana/dashboards/postgresql/QDevDORA.json | 1233 ++++++ .../dashboards/postgresql/SonarQubeCloud.json | 1503 +++++++ grafana/dashboards/postgresql/Sonarqube.json | 1098 +++++ .../postgresql/SteeringAdoptionTracker.json | 353 ++ grafana/dashboards/postgresql/TAPD.json | 1195 +++++ grafana/dashboards/postgresql/Taiga.json | 2442 +++++++++++ grafana/dashboards/postgresql/Teambition.json | 1195 +++++ grafana/dashboards/postgresql/Testmo.json | 1232 ++++++ .../dashboards/postgresql/WeeklyBugRetro.json | 2274 ++++++++++ .../postgresql/WeeklyCommunityRetro.json | 2447 +++++++++++ grafana/dashboards/postgresql/WorkLogs.json | 1467 +++++++ grafana/dashboards/postgresql/Zentao.json | 1195 +++++ .../dashboards/postgresql/qdev_executive.json | 768 ++++ .../postgresql/qdev_feature_metrics.json | 948 ++++ .../dashboards/postgresql/qdev_logging.json | 1118 +++++ .../dashboards/postgresql/qdev_user_data.json | 1182 +++++ .../postgresql/qdev_user_report.json | 464 ++ .../scripts/convert-mysql-to-postgresql.py | 667 +++ 60 files changed, 76682 insertions(+) create mode 100644 grafana/dashboards/postgresql/AICostEfficiency.json create mode 100644 grafana/dashboards/postgresql/AIModelROI.json create mode 100644 grafana/dashboards/postgresql/ArgoCD.json create mode 100644 grafana/dashboards/postgresql/Asana.json create mode 100644 grafana/dashboards/postgresql/AzureDevOps.json create mode 100644 grafana/dashboards/postgresql/Bamboo.json create mode 100644 grafana/dashboards/postgresql/BitBucket.json create mode 100644 grafana/dashboards/postgresql/CircleCI.json create mode 100644 grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json create mode 100644 grafana/dashboards/postgresql/ContributorExperience.json create mode 100644 grafana/dashboards/postgresql/DORA.json create mode 100644 grafana/dashboards/postgresql/DORAByTeam.json create mode 100644 grafana/dashboards/postgresql/DORADebug.json create mode 100644 grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json create mode 100644 grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json create mode 100644 grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json create mode 100644 grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json create mode 100644 grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json create mode 100644 grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json create mode 100644 grafana/dashboards/postgresql/DemoCommitCountByAuthor.json create mode 100644 grafana/dashboards/postgresql/DemoDetailedBugInfo.json create mode 100644 grafana/dashboards/postgresql/DemoHomepage.json create mode 100644 grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json create mode 100644 grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json create mode 100644 grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json create mode 100644 grafana/dashboards/postgresql/DeveloperProductivityHours.json create mode 100644 grafana/dashboards/postgresql/EngineeringOverview.json create mode 100644 grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json create mode 100644 grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json create mode 100644 grafana/dashboards/postgresql/GitHub.json create mode 100644 grafana/dashboards/postgresql/GithubCopilotAdoption.json create mode 100644 grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json create mode 100644 grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json create mode 100644 grafana/dashboards/postgresql/Gitlab.json create mode 100644 grafana/dashboards/postgresql/Homepage.json create mode 100644 grafana/dashboards/postgresql/Jenkins.json create mode 100644 grafana/dashboards/postgresql/Jira.json create mode 100644 grafana/dashboards/postgresql/KiroCreditsDORA.json create mode 100644 grafana/dashboards/postgresql/LanguageAIHeatmap.json create mode 100644 grafana/dashboards/postgresql/MultiAIComparison.json create mode 100644 grafana/dashboards/postgresql/Opsgenie.json create mode 100644 grafana/dashboards/postgresql/PagerDuty.json create mode 100644 grafana/dashboards/postgresql/QDevDORA.json create mode 100644 grafana/dashboards/postgresql/SonarQubeCloud.json create mode 100644 grafana/dashboards/postgresql/Sonarqube.json create mode 100644 grafana/dashboards/postgresql/SteeringAdoptionTracker.json create mode 100644 grafana/dashboards/postgresql/TAPD.json create mode 100644 grafana/dashboards/postgresql/Taiga.json create mode 100644 grafana/dashboards/postgresql/Teambition.json create mode 100644 grafana/dashboards/postgresql/Testmo.json create mode 100644 grafana/dashboards/postgresql/WeeklyBugRetro.json create mode 100644 grafana/dashboards/postgresql/WeeklyCommunityRetro.json create mode 100644 grafana/dashboards/postgresql/WorkLogs.json create mode 100644 grafana/dashboards/postgresql/Zentao.json create mode 100644 grafana/dashboards/postgresql/qdev_executive.json create mode 100644 grafana/dashboards/postgresql/qdev_feature_metrics.json create mode 100644 grafana/dashboards/postgresql/qdev_logging.json create mode 100644 grafana/dashboards/postgresql/qdev_user_data.json create mode 100644 grafana/dashboards/postgresql/qdev_user_report.json create mode 100755 grafana/scripts/convert-mysql-to-postgresql.py diff --git a/grafana/dashboards/postgresql/AICostEfficiency.json b/grafana/dashboards/postgresql/AICostEfficiency.json new file mode 100644 index 00000000000..50e462020c0 --- /dev/null +++ b/grafana/dashboards/postgresql/AICostEfficiency.json @@ -0,0 +1,537 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Kiro Usage Dashboard", + "type": "link", + "url": "/d/qdev_user_report" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Summary", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(SUM(credits_used)) AS 'Total Credits' FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Total Credits Used", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 1) AS 'Credits / PR' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per PR (Overall)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 1) AS 'Credits / Deploy' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per Deployment (Overall)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 1) AS 'Credits / Issue' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 10, + "panels": [], + "title": "Weekly Trends", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per merged PR", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(p.prs, 0), 0), 1) AS 'Credits per PR' FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Merged PR (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per production deployment", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(d.deploys, 0), 0), 1) AS 'Credits per Deploy' FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Deployment (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per resolved issue", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(i.resolved, 0), 0), 1) AS 'Credits per Issue' FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Is cost efficiency improving over time?", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS 'Credits', SUM(total_messages) AS 'Messages', SUM(chat_conversations) AS 'Conversations' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly AI Activity Volume", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "cost", + "efficiency" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "AI Cost-Efficiency (PostgreSQL)", + "uid": "ai_cost_efficiency-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/AIModelROI.json b/grafana/dashboards/postgresql/AIModelROI.json new file mode 100644 index 00000000000..e0759d89d7b --- /dev/null +++ b/grafana/dashboards/postgresql/AIModelROI.json @@ -0,0 +1,206 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Requests, avg prompt/response length, and usage share per model", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [] + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS 'Model', COUNT(*) AS 'Requests', ROUND(CAST(COUNT(*) * 100.0 AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)), 0), 1) AS 'Share %', ROUND(AVG(prompt_length)) AS 'Avg Prompt Len', ROUND(AVG(response_length)) AS 'Avg Response Len', ROUND(CAST(AVG(response_length) AS NUMERIC) / NULLIF(NULLIF(AVG(prompt_length), 0), 0), 2) AS 'Response/Prompt Ratio', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering Uses', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode Uses' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Model Performance Summary", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Request volume by model over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "bars", + "fillOpacity": 80, + "lineWidth": 1, + "stacking": { + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric, COUNT(*) AS value FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), model_id ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Model Usage Distribution", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Average response length per model over time \u2014 proxy for output quality", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric, ROUND(AVG(response_length)) AS value FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), model_id ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Avg Response Length by Model (Daily)", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "model", + "roi" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro AI Model ROI (PostgreSQL)", + "uid": "kiro_model_roi-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/ArgoCD.json b/grafana/dashboards/postgresql/ArgoCD.json new file mode 100644 index 00000000000..53f6936c925 --- /dev/null +++ b/grafana/dashboards/postgresql/ArgoCD.json @@ -0,0 +1,1033 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [ + { + "targetBlank": true, + "title": "ArgoCD", + "url": "https://devlake.apache.org/docs/Plugins/argocd" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: Track ArgoCD deployment activity, measure success rates, monitor environments, and spot slow deployments.\n- Data Source Required: ArgoCD plugin (deployments mapped into `cicd_deployments`).\n- Filters: Use the *Application* templating variable to focus on specific ArgoCD applications.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ',')))", + "refId": "A" + } + ], + "title": "1.1 Total Deployments", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ',')))", + "refId": "A" + } + ], + "title": "1.2 Successful Deployments", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Successful deployments / Total deployments", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 3 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT id), 0), 0) FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ',')))", + "refId": "A" + } + ], + "title": "1.3 Deployment Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 3 + }, + "id": 5, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT result, COUNT(DISTINCT id) AS deployment_count FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A" + } + ], + "title": "1.4 Deployment Result Distribution", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 6, + "panels": [], + "title": "2. Deployment Trends", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 7, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _deployments AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS deployment_count FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, deployment_count FROM _deployments ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "2.1 Deployments per Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 10 + }, + "id": 8, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _deployment_success_rate AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1, 2, 3) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Deployment Success Rate\" FROM _deployment_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "2.2 Deployment Success Rate by Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 10 + }, + "id": 15, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT environment, COUNT(DISTINCT id) AS deployment_count FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A" + } + ], + "title": "2.3 Deployments by Environment", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 9, + "panels": [], + "title": "3. Deployment Durations", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Average duration of deployments (minutes)", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 19 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL", + "refId": "A" + } + ], + "title": "3.1 Mean Deployment Duration (Minutes)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 6, + "y": 19 + }, + "id": 11, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _durations AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _durations ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "3.2 Mean Deployment Duration by Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 19 + }, + "id": 12, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "duration_minutes" + } + ] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT name AS deployment_name, display_title AS description, environment, result, CAST(duration_sec AS NUMERIC) / NULLIF(60, 0) AS duration_minutes, finished_date FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL ORDER BY duration_sec DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "3.3 Top 20 Longest Deployments", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "7-day rolling average of deployment duration (minutes)", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH daily AS (SELECT CAST(created_date AS DATE) AS day_bucket, AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS avg_duration_minutes FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL GROUP BY 1), rolling AS (SELECT day_bucket, AVG(avg_duration_minutes) OVER (ORDER BY day_bucket NULLS FIRST ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS rolling_avg_minutes FROM daily) SELECT day_bucket AS time, rolling_avg_minutes FROM rolling ORDER BY day_bucket NULLS FIRST", + "refId": "A" + } + ], + "title": "3.4 Rolling Deployment Duration (7-day Avg)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 13, + "panels": [], + "title": "4. Deployment Details", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 14, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "created_date" + } + ] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT created_date, name AS deployment_name, environment, result, status, display_title AS description, finished_date FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) ORDER BY created_date DESC NULLS LAST LIMIT 50", + "refId": "A" + } + ], + "title": "4.1 Recent Deployments", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 44 + }, + "id": 17, + "panels": [], + "title": "5. Images", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 45 + }, + "id": 18, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "deployment_created" + } + ] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT d.created_date AS deployment_created, d.name AS deployment_name, ri.images::text AS images, c.commit_sha AS revision, d.environment, d.result FROM cicd_deployments AS d LEFT JOIN cicd_deployment_commits AS c ON c.cicd_deployment_id = d.id LEFT JOIN _tool_argocd_revision_images AS ri ON ri.revision = c.commit_sha WHERE $__timeFilter(d.created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR d.cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) ORDER BY d.created_date DESC NULLS LAST LIMIT 50", + "refId": "A" + } + ], + "title": "5.1 Recent Deployment Images", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Approximation: counts deployments per unique images array (revision-level). For per-image counts, consider exploding arrays during ingestion.", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 45 + }, + "id": 19, + "options": { + "displayLabels": [ + "name", + "value" + ], + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH dep AS (SELECT d.id, c.commit_sha, d.cicd_scope_id FROM cicd_deployments AS d LEFT JOIN cicd_deployment_commits AS c ON c.cicd_deployment_id = d.id WHERE $__timeFilter(d.created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR d.cicd_scope_id = ANY(string_to_array('${application_id:raw}', ',')))), imgs AS (SELECT dep.id AS deployment_id, ri.images::text FROM dep LEFT JOIN _tool_argocd_revision_images AS ri ON ri.revision = dep.commit_sha WHERE NOT ri.images::text IS NULL) SELECT images AS image_array, COUNT(DISTINCT deployment_id) AS deployment_count FROM imgs GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "5.2 Top Image Arrays (Approx)", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "allValue": "$__all", + "definition": "select concat(name, '--', id) as text from cicd_scopes where id like 'argocd:ArgocdApplication:%'", + "hide": 0, + "includeAll": true, + "label": "Application", + "multi": true, + "name": "application_id", + "options": [], + "query": "select concat(name, '--', id) as text from cicd_scopes where id like 'argocd:ArgocdApplication:%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "ArgoCD (PostgreSQL)", + "uid": "Argocd001-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Asana.json b/grafana/dashboards/postgresql/Asana.json new file mode 100644 index 00000000000..d753420ec2d --- /dev/null +++ b/grafana/dashboards/postgresql/Asana.json @@ -0,0 +1,1192 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Asana", + "url": "https://devlake.apache.org/docs/Configuration/Asana" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic project management metrics from Asana.\n- Data Source Required: Asana", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Total number of issues created in the selected time range and board.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Delivered Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Issues' Lead Time are less than # days [Issues Resolved in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time [Issues Resolved in Selected Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "The cumulative distribution of issue lead time. Each point refers to the percent rank of a lead time.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.15", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of Issue Lead Time [Issues Resolved in Selected Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'asana%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'asana%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Asana (PostgreSQL)", + "uid": "asana-dashboard-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/AzureDevOps.json b/grafana/dashboards/postgresql/AzureDevOps.json new file mode 100644 index 00000000000..6f6fdd3351d --- /dev/null +++ b/grafana/dashboards/postgresql/AzureDevOps.json @@ -0,0 +1,2335 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 20, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 101, + "links": [ + { + "targetBlank": true, + "title": "Azure DevOps", + "url": "https://devlake.apache.org/docs/Plugins/azuredevops" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic Git and Code Review metrics from Azure DevOps.\n- Data Source Required: Azure DevOps", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 83, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Contribution (PRs)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 68, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Number of New Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Pull Request Count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 4 + }, + "id": 77, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.2 Number of New Pull Requests", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Merged PR Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 59, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.3 Top Contributors By Merged PRs", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 85, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. How PRs are handled?", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.05 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 17 + }, + "id": 66, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Ratio of unmerged Pull Requests of All Closed or Merged PRs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Open" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed without merging" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed and merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 17 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.2 Pull Request Status Distribution", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 23 + }, + "id": 80, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.3 Number of Pull Requests Closed without Merging [Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Ratio", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 50, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 23 + }, + "id": 81, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.4 Ratio of unmerged PRs of All Closed or Merged PRs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 29 + }, + "id": 72, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.5 Mean Time to Merge of Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 29 + }, + "id": 95, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.6 Mean Time to Merge of Pull Requests", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 102, + "panels": [], + "title": "3. CI/CD Metrics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Number of Pipeline runs executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 36 + }, + "id": 103, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of Successful Pipeline runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful Pipeline runs / Number of total Pipeline runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 36 + }, + "id": 104, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.2 Mean Pipeline runs Success Rate %", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 110, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.25, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.3 Number of successful and failed pipeline runs", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of successful Pipeline runs / Number of total Pipeline runs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 42 + }, + "id": 105, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.4 Total Pipeline runs Result Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Pipeline Runs success rate over time.\n2. The Pipeline runs being calculated are filtered by \"workflow runs starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 42 + }, + "id": 112, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id like \"%azure%\" */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.5 Pipeline run success rate %", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Pipeline runs success rate over time.\n2. The Pipeline runs being calculated are filtered by \"Pipeline runs starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 48 + }, + "id": 106, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.6 Mean Pipeline runs Duration in Minutes", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_sec" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 48 + }, + "id": 111, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id like \"%azure%\" */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.7 Mean pipeline run duration", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 99, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos where id like 'azure%'", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos where id like 'azure%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + "hide": 0, + "includeAll": false, + "label": "Time Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Azure DevOps (PostgreSQL)", + "uid": "ba7e3a95-80ed-4067-a54b-2a82758eb3dd-pg", + "version": 5, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Bamboo.json b/grafana/dashboards/postgresql/Bamboo.json new file mode 100644 index 00000000000..e4a202554cf --- /dev/null +++ b/grafana/dashboards/postgresql/Bamboo.json @@ -0,0 +1,1219 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 12, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 58, + "links": [ + { + "targetBlank": true, + "title": "Bamboo", + "url": "https://devlake.apache.org/docs/Plugins/bamboo" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic CI/CD metrics from Bamboo, such as [Build Count](https://devlake.apache.org/docs/Metrics/BuildCount), [Build Duration](https://devlake.apache.org/docs/Metrics/BuildDuration) and [Build Success Rate](https://devlake.apache.org/docs/Metrics/BuildSuccessRate). A build in Bamboo is an execution of a Bamboo Plan.\n- Data Source Required: Bamboo", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "Number of builds executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Total Number of Successful Builds [Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful builds / Number of total builds", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. Mean Build Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 3 + }, + "id": 37, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Total Build Result Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Number of successful builds / Number of total builds", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 3 + }, + "id": 55, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4. Mean Build Duration in Minutes", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 52, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Total Number of Successful Builds [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Build success rate over time.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Success Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Build Success Rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 50, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Build Success Rate [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 50, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 54, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of Successful and Failed Builds [Each Month]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Duration(minutes)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_minutes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 56, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.1 Mean Build Duration in Minutes [Each Month]", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 60, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"bamboo%\" ", + "hide": 0, + "includeAll": true, + "label": "Plan Name", + "multi": true, + "name": "plan_id", + "options": [], + "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"bamboo%\" ", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Bamboo (PostgreSQL)", + "uid": "a90e58d9-7acc-4858-aa77-f606d11a7d4a-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/BitBucket.json b/grafana/dashboards/postgresql/BitBucket.json new file mode 100644 index 00000000000..31e240eba6f --- /dev/null +++ b/grafana/dashboards/postgresql/BitBucket.json @@ -0,0 +1,2335 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 37, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 101, + "links": [ + { + "targetBlank": true, + "title": "BitBucket Cloud", + "url": "https://devlake.apache.org/docs/Plugins/bitbucket" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic Git and Code Review metrics from Bitbucket.\n- Data Source Required: Bitbucket Cloud or Server/data center. Please note that for Bitbucket Server/Data center, there is no embedded CI/CD data.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 83, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Contribution (PRs)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 68, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Number of New Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Pull Request Count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 4 + }, + "id": 77, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.2 Number of New Pull Requests", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Merged PR Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 59, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.3 Top 20 Contributors By Merged PRs", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 85, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. How PRs are handled?", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.05 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 17 + }, + "id": 66, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Ratio of unmerged Pull Requests of All Closed or Merged PRs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Open" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed without merging" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed and merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 17 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.2 Pull Request Status Distribution", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 23 + }, + "id": 80, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.3 Number of Pull Requests Closed without Merging [Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Ratio", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 50, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 23 + }, + "id": 81, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.4 Ratio of Non-merging PRs of All Closed or Merged PRs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 29 + }, + "id": 72, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.5 Mean Time to Merge of Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 29 + }, + "id": 95, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.6 Mean Time to Merge of Pull Requests", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 109, + "panels": [], + "title": "CI/CD Metrics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Number of pipeline runs executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 36 + }, + "id": 102, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of successful pipeline runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful pipeline runs / Number of total pipeline runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 36 + }, + "id": 103, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.2 Mean pipeline run success rate %", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 104, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.25, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.3 Number of successful and failed pipeline runs", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 42 + }, + "id": 105, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.4 Pipeline runs result distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Pipeline Runs success rate over time.\n2. The Pipeline runs being calculated are filtered by \"workflow runs starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 42 + }, + "id": 106, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.5 Pipeline run success rate %", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of successful pipeline runs / Number of total pipeline runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 48 + }, + "id": 107, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.6 Mean pipeline runs duration", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_sec" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 48 + }, + "id": 108, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.7 Mean pipeline run duration", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 99, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos where id like 'bitbucket%'", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos where id like 'bitbucket%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + "hide": 0, + "includeAll": false, + "label": "Time Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Bitbucket (PostgreSQL)", + "uid": "4LzQHZa4k-pg", + "version": 5, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/CircleCI.json b/grafana/dashboards/postgresql/CircleCI.json new file mode 100644 index 00000000000..4213b03c6f9 --- /dev/null +++ b/grafana/dashboards/postgresql/CircleCI.json @@ -0,0 +1,1272 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 27, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 58, + "links": [ + { + "targetBlank": true, + "title": "CircleCI", + "url": "https://devlake.apache.org/docs/Plugins/circleci" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic CI/CD metrics from CircleCI, such as [Build Count](https://devlake.apache.org/docs/Metrics/BuildCount), [Build Duration](https://devlake.apache.org/docs/Metrics/BuildDuration) and [Build Success Rate](https://devlake.apache.org/docs/Metrics/BuildSuccessRate).\n- Data Source Required: CircleCI", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "Number of builds executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Total Number of Successful Workflows [Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful workflows / Number of total workflows", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. Mean Workflow Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 3 + }, + "id": 37, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Total Workflow Result Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Number of successful workflows / Number of total workflows", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 3 + }, + "id": 55, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4. Mean Workflow Duration in Minutes", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 52, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Total Number of Successful Workflows [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "1. Mean workflows success rate over time.\n2. The workflows being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Workflow Success Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Workflow Success Rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 50, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Workflow Success Rate [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 50, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 54, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of Successful and Failed Workflows [Each Month]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_minutes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 56, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.1 Mean Workflow Duration in Minutes [Each Month]", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 60, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"%circleci%\" ", + "hide": 0, + "includeAll": true, + "label": "CircleCI Project", + "multi": true, + "name": "full_name", + "options": [], + "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"%circleci%\" ", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "CircleCI (PostgreSQL)", + "uid": "CircleCI-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json b/grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json new file mode 100644 index 00000000000..f5fc5c019ca --- /dev/null +++ b/grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json @@ -0,0 +1,1443 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "This dashboard shows the metrics collected by gitextractor plugin", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 40, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Author dimension", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 16, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT author_name, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY author_name, author_id ORDER BY commit_nums DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "commits distribution by author", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 79, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT author_name, SUM(additions - deletions) AS cnt FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY author_name, author_id ORDER BY cnt DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "lines of code distribution by author", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 20, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Time dimension", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 22, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS weekday, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE $__timeFilter(commits.authored_date) AND commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY weekday", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "commits distribution", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 24, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS weekday, SUM(additions - deletions) AS changed_nums, SUM(additions) AS total_additions, SUM(deletions) AS total_deletions FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY weekday ORDER BY weekday NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "lines of code distribution", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 14, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "File dimension", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 12, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.5, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT file_path, COUNT(DISTINCT c.author_name) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path ORDER BY cnt DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "_raw_data_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "issue_commits", + "timeColumn": "id", + "timeColumnType": "bigint", + "where": [] + } + ], + "title": "Files with maximum number of authors", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 19 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT file_path, COUNT(DISTINCT author_name) AS author_count, MAX(rst) AS lines_of_code, CAST(MAX(rst) AS NUMERIC) / NULLIF(COUNT(DISTINCT author_name), 0) AS rate FROM commits JOIN (SELECT file_path, commit_files.commit_sha, SUM(additions - deletions) AS rst FROM commit_files JOIN repo_commits AS rc ON commit_files.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path, commit_files.commit_sha) AS a ON a.commit_sha = commits.sha GROUP BY file_path HAVING author_count > 0 ORDER BY rate DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "[Selected Files] Largest files with lowest number of authors", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 10, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS modified_num FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path ORDER BY modified_num DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Files with most modifications", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 8, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) GROUP BY file_path ORDER BY cnt DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "[All Files] Files with most modifications", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 26, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Lineage dimension", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 28, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.5, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(commits.committed_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS wd, COUNT(*) AS lived_lines FROM repo_snapshot JOIN commits ON commits.sha = repo_snapshot.commit_sha WHERE repo_snapshot.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.committed_date) GROUP BY wd ORDER BY wd NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "In which day the code has highest chance to stay in repository.", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Please disable the SkipCommitFiles in the .env setting to view this metric. However, this will significantly increase the time to collect data.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 30, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT file_path, AVG((EXTRACT(EPOCH FROM (NOW() - commits.committed_date))/86400)) AS line_age FROM repo_snapshot JOIN commits ON repo_snapshot.commit_sha = commits.sha WHERE repo_snapshot.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.committed_date) GROUP BY file_path ORDER BY line_age DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Which file has the longest average line age?", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": ".*", + "value": ".*" + }, + "description": "input file path", + "hide": 0, + "label": "", + "name": "selected_path", + "options": [ + { + "selected": false, + "text": "", + "value": "" + } + ], + "query": ".*", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-5y", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Component and File-level Metrics (PostgreSQL)", + "uid": "KxUh7IG4z-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/ContributorExperience.json b/grafana/dashboards/postgresql/ContributorExperience.json new file mode 100644 index 00000000000..5aa0a98cbc9 --- /dev/null +++ b/grafana/dashboards/postgresql/ContributorExperience.json @@ -0,0 +1,930 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 7, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 24, + "links": [], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard answers the question \"What makes a great developer experience? And how can we define and track that?\". This dashboard heavily focuses on actionability. All metrics can be deterministically improved as long as OSS maintainers invested time into them.\n- Data Source Required: GitHub", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 8, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": { + "titleSize": 2 + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id = ANY(ARRAY[${repo_id}]::text[])) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Time To Initial Issue Response [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 4 + }, + "id": 4, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = \"DONE\" AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Resolution Time [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 95 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 4 + }, + "id": 12, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id = ANY(ARRAY[${repo_id}]::text[])) SELECT CAST(100 * SUM(CASE WHEN CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(60, 0) < $iir_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM issue_comment_list WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Response Rate within SLA [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 4 + }, + "id": 10, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = \"$label_gfi\" AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Good First Issues [Outstanding]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 12 + }, + "id": 16, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH pr_comment_list AS (SELECT pr.id AS issue_id, pr.url, pr.title, pr.created_date AS pr_created_date, prc.id AS comment_id, prc.created_date AS comment_date, prc.account_id, CASE WHEN NOT prc.id IS NULL THEN RANK() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE CAST(pr.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - pr_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM pr_comment_list WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Time to Initial PR Review [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 12 + }, + "id": 14, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Resolution Time [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 95 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 12 + }, + "id": 18, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Resolution Rate within SLA [Last Month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 12 + }, + "id": 20, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Closed W/O Merging Ratio [Last Month]", + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 22, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "OSS Maintainer Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select concat(name, '-', id) from repos", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '-', id) from repos", + "refresh": 1, + "regex": "/^(?.*)-(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "24", + "value": "24" + }, + "hide": 0, + "label": "Time to Initial Issue Response SLA (in hours)", + "name": "iir_sla", + "options": [ + { + "selected": true, + "text": "24", + "value": "24" + } + ], + "query": "24", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "7", + "value": "7" + }, + "hide": 0, + "label": "PR Resolution Time SLA (in days)", + "name": "prrt_sla", + "options": [ + { + "selected": true, + "text": "7", + "value": "7" + } + ], + "query": "7", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": false, + "text": "good first issue", + "value": "good first issue" + }, + "hide": 0, + "label": "Label for good first issues", + "name": "label_gfi", + "options": [ + { + "selected": true, + "text": "good first issue", + "value": "good first issue" + } + ], + "query": "good first issue", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Contributor Experience (PostgreSQL)", + "uid": "bwsP5Nz4z-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORA.json b/grafana/dashboards/postgresql/DORA.json new file mode 100644 index 00000000000..0b044b968f1 --- /dev/null +++ b/grafana/dashboards/postgresql/DORA.json @@ -0,0 +1,1544 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 41, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 16, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See [how to config](https://devlake.apache.org/docs/DORA) this dashboard\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, webhook, etc. \n - `Pull Requests` from GitHub PRs, GitLab MRs, BitBucket PRs, Azure DevOps PRs, etc.\n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.\n- You can validate/debug this dashboard with the [DORA validation dashboard](/grafana/d/KGkUnV-Vz/dora-dashboard-validation)\n- DORA benchmarks vary in different years. You can switch the benchmarks to change them.\n- In DORA's official report in 2023, metric 'failed deployment recovery time' has replaced 'MTTR'.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "noValue": "-", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "low" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "medium" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "high" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "elite" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": false, + "title": "", + "url": "/d/${__data.fields[\"metric_hidden\"]}?${project:queryparam}&from=${__from}&to=${__to}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n SELECT\n cdc.cicd_deployment_id as deployment_id,\n max(DATE(cdc.finished_date)) as day\n FROM\n cicd_deployment_commits cdc\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id\n and pm.`table` = 'cicd_scopes'\n WHERE\n pm.project_name in (${project})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY\n 1\n),\n_days_weekly_deploy as(\n -- calculate the number of deployment days every week\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - WEEKDAY(last_few_calendar_months.day) DAY\n )\n ) as week,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n 0\n )\n ) as weeks_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n week\n),\n_days_monthly_deploy as(\n -- calculate the number of deployment days every month\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - DAY(last_few_calendar_months.day) + 1 DAY\n )\n ) as month,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n null\n )\n ) as months_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n month\n),\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY\n month DESC\n ) AS rn\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_week_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_weekly_deploy\n),\n_median_number_of_deployment_days_per_week as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_week\n FROM\n _median_number_of_deployment_days_per_week_ranks\n WHERE\n ranks <= 0.5\n),\n_median_number_of_deployment_days_per_month_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_month as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_month\n FROM\n _median_number_of_deployment_days_per_month_ranks\n WHERE\n ranks <= 0.5\n),\n_days_per_six_months_deploy_by_filter AS (\n SELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\n FROM\n _days_six_months_deploy\n WHERE\n rn % 6 = 1\n),\n_median_number_of_deployment_days_per_six_months_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed_per_six_months\n ) as ranks\n FROM\n _days_per_six_months_deploy_by_filter\n),\n_median_number_of_deployment_days_per_six_months as(\n SELECT\n min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months,\n min(months_deployed_count) as is_collected\n FROM\n _median_number_of_deployment_days_per_six_months_ranks\n WHERE\n ranks >= 0.5\n),\n_metric_deployment_frequency as (\n SELECT\n 'Deployment frequency' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n WHEN median_number_of_deployment_days_per_month < 1\n and is_collected is not null THEN 'Fewer than once per month(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n WHEN median_number_of_deployment_days_per_six_months < 1\n and is_collected is not null THEN 'Fewer than once per six months(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_number_of_deployment_days_per_week,\n _median_number_of_deployment_days_per_month,\n _median_number_of_deployment_days_per_six_months\n),\n-- Metric 2: median lead time for changes\n_pr_stats as (\n -- get the cycle time of PRs deployed by the deployments finished in the selected period\n SELECT\n distinct pr.id,\n ppm.pr_cycle_time\n FROM\n pull_requests pr\n join project_pr_metrics ppm on ppm.id = pr.id\n join project_mapping pm on pr.base_repo_id = pm.row_id\n and pm.`table` = 'repos'\n join cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n WHERE\n pm.project_name in (${project})\n and pr.merged_date is not null\n and ppm.pr_cycle_time is not null\n and $__timeFilter(cdc.finished_date)\n),\n_median_change_lead_time_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n pr_cycle_time\n ) as ranks\n FROM\n _pr_stats\n),\n_median_change_lead_time as(\n -- use median PR cycle time as the median change lead time\n SELECT\n max(pr_cycle_time) as median_change_lead_time\n FROM\n _median_change_lead_time_ranks\n WHERE\n ranks <= 0.5\n),\n_metric_change_lead_time as (\n SELECT\n 'Lead time for changes' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_change_lead_time\n),\n-- Metric 3: change failure rate\n_deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.05 THEN \"0-5%(elite)\" WHEN change_failure_rate <= 0.10 THEN \"5%-10%(high)\" WHEN change_failure_rate <= 0.15 THEN \"10%-15%(medium)\" WHEN change_failure_rate > 0.15 THEN \"> 15%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.15 THEN \"0-15%(elite)\" WHEN change_failure_rate <= 0.20 THEN \"16%-20%(high)\" WHEN change_failure_rate <= 0.30 THEN \"21%-30%(medium)\" WHEN change_failure_rate > 0.30 THEN \"> 30%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\" WHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\" WHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Overall DORA Metrics", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "How often an organization deploys code to production or release it to end users\n\n# 2023 Benchmarks\n- **Elite**: >= 7 deployment days per week\n- **High**: >= 1 deployment day per week\n- **Medium**: >= 1 deployment day per month\n- **Low**: < 1 deployment day per month\n# 2021 Benchmarks\n- **Elite**: >= 7 deployment days per week\n- **High**: >= 1 deployment day per month\n- **Medium**: >= 1 deployment day per six months\n- **Low**: < 1 deployment day per six months", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 13 + }, + "id": 11, + "links": [ + { + "targetBlank": false, + "title": "link", + "url": "/d/Deployment-frequency/dora-drill-down-deployment-frequency?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Deployment Frequency$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_7_PLACEHOLDER deployment days per week(high)STRING_LITERAL_8_PLACEHOLDER deployment days per month(medium)STRING_LITERAL_9_PLACEHOLDER deployment days per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_13_PLACEHOLDER deployment days per month(high)STRING_LITERAL_14_PLACEHOLDER deployment days per six months(medium)STRING_LITERAL_15_PLACEHOLDER deployment days per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Deployment Frequency", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "The median amount of time for a code change to be deployed into production.\n\n\n# 2023 Benchmarks\n- **Elite**: Less than one day\n- **High**: Between one day and one week\n- **Medium**: Between one week and one month\n- **Low**: More than one month\n# 2021 Benchmarks\n- **Elite**: Less than one hour\n- **High**: Less than one week\n- **Medium**: Between one week and six months\n- **Low**: More than six months\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 13 + }, + "id": 12, + "links": [ + { + "title": "link", + "url": "/d/Lead-time-for-changes/dora-drill-down-lead-time-for-changes?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Median Lead Time for Changes In Hours", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "The percentage of changes that were made to a code that then resulted in incidents, rollbacks, or any type of production failure.\n\n# 2023 Benchmarks\n- **Elite**: (0, 5%]\n- **High**: (5%, 10%]\n- **Medium**: (10%, 15%]\n- **Low**: (15%, 100%]\n# 2021 Benchmarks\n- **Elite**: (0, 15%]\n- **High**: (16%, 20%]\n- **Medium**: (21%, 30%]\n- **Low**: (30%, 100%]\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 13 + }, + "id": 14, + "links": [ + { + "title": "link", + "url": "/d/Change-failure-rate/dora-drill-down-change-failure-rate?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^change_failure_rate$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERNo dataSTRING_LITERAL_11_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Change Failure Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "# MTTR benchmarks:\ndevlake.apache.org/docs/Metrics/MTTR#how-is-it-calculated\n# FDRT benchmarks:\ndevlake.apache.org/docs/Metrics/FailedDeploymentRecoveryTime#how-is-it-calculated\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 17, + "links": [ + { + "title": "Failed Deployment Recovery Time", + "url": "/d/Failed-deployment-recovery-time/dora-details-failed-deployment-recovery-time?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + }, + { + "title": "Median Time to Restore Service", + "url": "/d/Time-to-restore-service/dora-details-median-time-to-restore-service?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"No data\" END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "${title_value} In Hours", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 2, + "links": [ + { + "title": "link", + "url": "/d/Deployment-frequency/dora-drill-down-deployment-frequency?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Monthly deployments", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 6, + "links": [ + { + "title": "link", + "url": "/d/Lead-time-for-changes/dora-drill-down-lead-time-for-changes?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hours' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Median Lead Time for Changes", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "change_failure_rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 5, + "links": [ + { + "title": "link", + "url": "/d/Change-failure-rate/dora-drill-down-change-failure-rate?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Change Failure Rate", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "median_time_to_resolve_in_hour" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 9, + "links": [ + { + "title": "Failed Deployment Recovery Time", + "url": "/d/Failed-deployment-recovery-time/dora-details-failed-deployment-recovery-time?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + }, + { + "title": "Median Time To Restore Service", + "url": "/d/Time-to-restore-service/dora-details-median-time-to-restore-service?orgId=1&${project:queryparam}&from=${__from}&to=${__to}" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "${title_value}", + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard", + "Highlights", + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA (PostgreSQL)", + "uid": "qNo8_0M4z-pg", + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORAByTeam.json b/grafana/dashboards/postgresql/DORAByTeam.json new file mode 100644 index 00000000000..03c39ee9346 --- /dev/null +++ b/grafana/dashboards/postgresql/DORAByTeam.json @@ -0,0 +1,1254 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 43, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 16, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See [how to config](https://devlake.apache.org/docs/DORA) this dashboard\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, webhook, etc. \n - `Pull Requests` from GitHub PRs, GitLab MRs, BitBucket PRs, Azure DevOps PRs, etc.\n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.\n- You can validate/debug this dashboard with the [DORA validation dashboard](/grafana/d/KGkUnV-Vz/dora-dashboard-validation) \n- You also need to do [team configuration](https://devlake.apache.org/docs/Configuration/TeamConfiguration) to use this dashboard. \n- DORA benchmarks vary in different years. You can switch the benchmarks to change them.\n- In DORA's official report in 2023, metric 'failed deployment recovery time' has replaced 'MTTR'.\n- How does this work? \n - Gets the author of the specific commit and then navigates to the team the user belongs to. \n - Gets the team from the PR's author. \n - Gets the team from the commit author.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "noValue": "-", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "low" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "medium" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "high" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "elite" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n SELECT\n cdc.cicd_deployment_id as deployment_id,\n max(DATE(cdc.finished_date)) as day\n FROM\n cicd_deployment_commits cdc\n JOIN commits c on cdc.commit_sha = c.sha\n join user_accounts ua on c.author_id = ua.account_id\n join users u on ua.user_id = u.id\n join team_users tu on u.id = tu.user_id\n join teams t on tu.team_id = t.id\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id\n and pm.`table` = 'cicd_scopes'\n WHERE\n t.name in (${team})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY\n 1\n),\n_days_weekly_deploy as(\n -- calculate the number of deployment days every week\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - WEEKDAY(last_few_calendar_months.day) DAY\n )\n ) as week,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n 0\n )\n ) as weeks_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n week\n),\n_days_monthly_deploy as(\n -- calculate the number of deployment days every month\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - DAY(last_few_calendar_months.day) + 1 DAY\n )\n ) as month,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n null\n )\n ) as months_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n month\n),\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY\n month DESC\n ) AS rn\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_week_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_weekly_deploy\n),\n_median_number_of_deployment_days_per_week as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_week\n FROM\n _median_number_of_deployment_days_per_week_ranks\n WHERE\n ranks <= 0.5\n),\n_median_number_of_deployment_days_per_month_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_month as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_month\n FROM\n _median_number_of_deployment_days_per_month_ranks\n WHERE\n ranks <= 0.5\n),\n_days_per_six_months_deploy_by_filter AS (\n SELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\n FROM\n _days_six_months_deploy\n WHERE\n rn % 6 = 1\n),\n_median_number_of_deployment_days_per_six_months_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed_per_six_months\n ) as ranks\n FROM\n _days_per_six_months_deploy_by_filter\n),\n_median_number_of_deployment_days_per_six_months as(\n SELECT\n min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months,\n min(months_deployed_count) as is_collected\n FROM\n _median_number_of_deployment_days_per_six_months_ranks\n WHERE\n ranks >= 0.5\n),\n_metric_deployment_frequency as (\n SELECT\n 'Deployment frequency' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n WHEN median_number_of_deployment_days_per_month < 1\n and is_collected is not null THEN 'Fewer than once per month(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n WHEN median_number_of_deployment_days_per_six_months < 1\n and is_collected is not null THEN 'Fewer than once per six months(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_number_of_deployment_days_per_week,\n _median_number_of_deployment_days_per_month,\n _median_number_of_deployment_days_per_six_months\n),\n-- Metric 2: median lead time for changes\n_pr_stats as (\n -- get the cycle time of PRs deployed by the deployments finished in the selected period\n SELECT\n distinct pr.id,\n ppm.pr_cycle_time\n FROM\n pull_requests pr\n join user_accounts ua on pr.author_id = ua.account_id\n join users u on ua.user_id = u.id\n join team_users tu on u.id = tu.user_id\n join teams t on tu.team_id = t.id\n join project_pr_metrics ppm on ppm.id = pr.id\n join project_mapping pm on pr.base_repo_id = pm.row_id\n and pm.`table` = 'repos'\n join cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n WHERE\n t.name in (${team})\n and pr.merged_date is not null\n and ppm.pr_cycle_time is not null\n and $__timeFilter(cdc.finished_date)\n),\n_median_change_lead_time_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n pr_cycle_time\n ) as ranks\n FROM\n _pr_stats\n),\n_median_change_lead_time as(\n -- use median PR cycle time as the median change lead time\n SELECT\n max(pr_cycle_time) as median_change_lead_time\n FROM\n _median_change_lead_time_ranks\n WHERE\n ranks <= 0.5\n),\n_metric_change_lead_time as (\n SELECT\n 'Lead time for changes' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_change_lead_time\n),\n-- Metric 3: change failure rate\n_deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.05 THEN \"0-5%(elite)\" WHEN change_failure_rate <= 0.10 THEN \"5%-10%(high)\" WHEN change_failure_rate <= 0.15 THEN \"10%-15%(medium)\" WHEN change_failure_rate > 0.15 THEN \"> 15%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.15 THEN \"0-15%(elite)\" WHEN change_failure_rate <= 0.20 THEN \"16%-20%(high)\" WHEN change_failure_rate <= 0.30 THEN \"21%-30%(medium)\" WHEN change_failure_rate > 0.30 THEN \"> 30%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\" WHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\" WHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Overall DORA Metrics", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Deployment Frequency$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_7_PLACEHOLDER deployment days per week(high)STRING_LITERAL_8_PLACEHOLDER deployment days per month(medium)STRING_LITERAL_9_PLACEHOLDER deployment days per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_13_PLACEHOLDER deployment days per month(high)STRING_LITERAL_14_PLACEHOLDER deployment days per six months(medium)STRING_LITERAL_15_PLACEHOLDER deployment days per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Deployment Frequency", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^median_change_lead_time$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Median Lead Time for Changes", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 16 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^change_failure_rate$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "-- Metric 4: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERInvalid dora reportSTRING_LITERAL_11_PLACEHOLDER", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Change Failure Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 16 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^median_time_in_hour$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "${title_value}", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 21 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Monthly deployments", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 21 + }, + "id": 6, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hour' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Median Lead Time for Changes", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "change_failure_rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 29 + }, + "id": 5, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "-- Metric 4: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Change Failure Rate", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 29 + }, + "id": 9, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "${title_value}", + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from teams", + "hide": 0, + "includeAll": true, + "label": "Team", + "multi": false, + "name": "team", + "options": [], + "query": "select distinct name from teams", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA (by Team) (PostgreSQL)", + "uid": "66YkL8y4z-pg", + "version": 4, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADebug.json b/grafana/dashboards/postgresql/DORADebug.json new file mode 100644 index 00000000000..1b720a795ca --- /dev/null +++ b/grafana/dashboards/postgresql/DORADebug.json @@ -0,0 +1,3863 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 44, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard is designed to validate the [DORA dashboard](/grafana/d/qNo8_0M4z/dora?orgId=1).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 2 + }, + "id": 20, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Check \"Deployment Frequency\"", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 64, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See the definition and calculation logic of [Deployment Frequency](https://devlake.apache.org/docs/Metrics/DeploymentFrequency)\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, BitBucket Pipelines, or Webhook, etc. \n- Transformation Required: Define `deployments` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 1 + }, + "PRODUCTION": { + "color": "green", + "index": 0 + }, + "SUCCESS": { + "color": "green", + "index": 2 + }, + "This project is selected": { + "color": "green", + "index": 3 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 0, + "y": 6 + }, + "id": 16, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE $__timeFilter(cdc.finished_date) GROUP BY pm.project_name, select_status, _raw_data_table, result, environment", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 1. All cicd_deployment_commits (the rows with 3 green columns will be used in the following steps)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 6 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Deployment Frequency$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDEROn-demand(elite)STRING_LITERAL_7_PLACEHOLDERBetween once per day and once per week(high)STRING_LITERAL_8_PLACEHOLDERBetween once per week and once per month(medium)STRING_LITERAL_9_PLACEHOLDERFewer than once per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDEROn-demand(elite)STRING_LITERAL_13_PLACEHOLDERBetween once per day and once per month(high)STRING_LITERAL_14_PLACEHOLDERBetween once per month and once every 6 months(medium)STRING_LITERAL_15_PLACEHOLDERFewer than once per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 1 - Deployment Frequency", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 1 + }, + "PRODUCTION": { + "color": "green", + "index": 0 + }, + "SUCCESS": { + "color": "green", + "index": 2 + }, + "This project is selected": { + "color": "green", + "index": 3 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 0, + "y": 11 + }, + "id": 29, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 2. Find the number of successful production deployments in this project (Last column)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 15 + }, + "id": 34, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 2 - Monthly deployments", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 1 + }, + "PRODUCTION": { + "color": "green", + "index": 0 + }, + "SUCCESS": { + "color": "green", + "index": 2 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "deployment_id" + }, + "properties": [ + { + "id": "custom.width" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 0, + "y": 16 + }, + "id": 49, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 3. Use the last finished_date of deployment commits as the finished date of deployments in this project", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "Between once per month and once every 6 months": { + "color": "yellow", + "index": 1 + }, + "Between once per week and once per month": { + "color": "green", + "index": 2 + }, + "Fewer than once per six months": { + "color": "red", + "index": 0 + }, + "On-demand": { + "color": "purple", + "index": 3 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "day" + }, + "properties": [ + { + "id": "custom.width" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 21 + }, + "id": 11, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4. Daily deployments in this project [To check Figure 1]", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "Between once per month and once every 6 months": { + "color": "yellow", + "index": 1 + }, + "Between once per week and once per month": { + "color": "green", + "index": 2 + }, + "Fewer than once per six months": { + "color": "red", + "index": 0 + }, + "On-demand": { + "color": "purple", + "index": 3 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "day" + }, + "properties": [ + { + "id": "custom.width" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 21 + }, + "id": 50, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(day, '%y/%m') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 5. Monthly deployments in this project [To check Figure 2]", + "type": "table" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 28, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Check \"Median Lead Time for Changes\"", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 72, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See the definition and calculation logic of [Median Lead Time for Changes](https://devlake.apache.org/docs/Metrics/LeadTimeForChanges)\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, BitBucket Pipelines, or Webhook, etc. \n - `Pull Requests` from GitHub PRs, GitLab MRs, BitBucket PRs, Azure DevOps PRs, etc.\n- Transformation Required: Define `deployments` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.\n- Validatation Steps below:\n - Step 1 - check the data integrity of PRs in table `pull_requests`\n - Step 2 - check the data integrity of deployment_commit in table `cicd_deployment_commits`\n - Step 3 - check if a deployment_commit is associated with the correct PR in table `project_pr_metrics`\n - Step 4 - check if metrics like PR Coding/Pickup/Review/Deploy/cycle time are correct in table `project_pr_metrics`\n - Step 5 - check if the median lead time for changes in each month is correct", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 254, 254, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 0, + "y": 34 + }, + "id": 18, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, pr._raw_data_table, COUNT(*) AS total_number_of_PRs FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND /* \tand pr.merged_date is not null */ /* \tand prm.pr_cycle_time is not null */ $__timeFilter(pr.created_date) GROUP BY 1, 2", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 1-1. Check if the total PR number in the selected project(s) is correct", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 34 + }, + "id": 40, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^median_change_lead_time$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\" WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\" WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\" WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\" ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\" WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\" WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\" WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\" ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 3 - Median Lead Time for Changes", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": false, + "inspect": false + }, + "mappings": [ + { + "options": { + "This Project": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "merged_date" + }, + "properties": [ + { + "id": "color" + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "green", + "value": 0 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "project_name" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 16, + "x": 0, + "y": 39 + }, + "id": 53, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, pr.title /* pr.status, */, pr.author_name, pr.url, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pr.id /* pm.project_name in ($project) */ = '$pr_id' AND $__timeFilter(pr.created_date)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 1-2. Check if the attributes of the selected pull request is correct (Use the Pull Request URL filter above)", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 40 + }, + "id": 38, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hour' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 4 - Median Lead Time for Changes", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 254, 254, 1)", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "from": 1, + "result": { + "color": "green", + "index": 0 + }, + "to": 10000000 + }, + "type": "range" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 42 + }, + "id": 12, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS 'No. of merged PRs in table.pull_requests' FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'No. of PRs in table.project_pr_metrics' FROM project_pr_metrics WHERE project_name IN ($project)", + "refId": "B", + "select": [ + [ + { + "params": [ + "blueprint_id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprint_labels", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 1-3. Check if the No. of records in project_pr_metrics is the same as the No. of MERGED PRs in the selected project(s)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 1 + }, + "PRODUCTION": { + "color": "green", + "index": 0 + }, + "SUCCESS": { + "color": "green", + "index": 2 + }, + "This project is selected": { + "color": "green", + "index": 3 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 46 + }, + "id": 68, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 2. Check if the number of successful production deployments in the selected project(s) is correct", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "This Project": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "merged_date" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + }, + { + "id": "color" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "green", + "value": 0 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "select_status" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "This project is selected": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 47 + }, + "id": 51, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, pr.title /* pr.status, */, pr.url /* \tpr.author_name, */, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE $__timeFilter(pr.created_date) /* pm.project_name in ($project) */", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Appendix 1 - All PRs in this project (Only the rows with 2 green columns should appear in project_pr_metrics)", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "NO": { + "color": "red", + "index": 1 + }, + "YES": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 50 + }, + "id": 69, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* This query can be used to test if the column \"deployment_commit_id\" is associated with the correct PR */ WITH pr_merge_commits AS (SELECT ppm.id AS pr_id, ppm.deployment_commit_id AS id_1, pr.merge_commit_sha, ppm.project_name FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ppm.project_name IN ($project) AND ppm.id = '$pr_id'), _deployment_commits AS (SELECT DISTINCT cdc1.id AS id_2, cdc1.prev_success_deployment_commit_id, cdc1.commit_sha AS new_commit_sha, cdc2.commit_sha AS old_commit_sha, cdc1.finished_date, cd.commit_sha AS deployed_commits FROM cicd_deployment_commits AS cdc1 LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc1.prev_success_deployment_commit_id = cdc2.id JOIN commits_diffs AS cd ON cdc1.commit_sha = cd.new_commit_sha AND COALESCE(cdc2.commit_sha, '') = cd.old_commit_sha JOIN project_mapping AS pm ON cdc1.cicd_scope_id = pm.row_id WHERE cdc1.result = 'SUCCESS' AND cdc1.environment = 'PRODUCTION' AND pm.project_name IN ($project)), _find_deployment_commit_id_from_pr AS (SELECT pmc.pr_id, pmc.id_1, pmc.merge_commit_sha, pmc.project_name, dc.id_2, dc.prev_success_deployment_commit_id, dc.deployed_commits, RANK() OVER (PARTITION BY pr_id ORDER BY dc.finished_date NULLS FIRST) AS deployment_rank FROM pr_merge_commits AS pmc LEFT JOIN _deployment_commits AS dc ON pmc.merge_commit_sha = dc.deployed_commits) SELECT pr_id, id_1 AS deployment_commit_id /* If \"id_1\" equals \"id_2\", then pass */, CASE WHEN id_1 = COALESCE(id_2, '') THEN 'YES' ELSE 'NO' END AS if_the_mapping_logic_is_correct /* \tid_2, */ FROM _find_deployment_commit_id_from_pr WHERE deployment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 3. Check if a `pull request` is associated with the correct `depoloyment commit` in table project_pr_metrics", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 54 + }, + "id": 52, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _pr_commit_ranks AS (SELECT pr.id, pr.created_date AS pr_created_date, prc.commit_sha, prc.commit_authored_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.commit_authored_date ASC NULLS FIRST) AS commit_rank FROM pull_requests AS pr LEFT JOIN pull_request_commits AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id') SELECT id, CEIL(CAST(EXTRACT(EPOCH FROM (pr_created_date - commit_authored_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR coding time from PRs and commits' /* commit_sha as first_commit_sha, */ /* commit_authored_date as first_commit_authored_date, */ FROM _pr_commit_ranks WHERE commit_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT id, pr_coding_time AS 'PR coding time from project_pr_metrics' /* first_commit_sha, */ FROM project_pr_metrics WHERE id = '$pr_id'", + "refId": "B", + "select": [ + [ + { + "params": [ + "blueprint_id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprint_labels", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4-1. Check if PR coding time in project_pr_metrics is correct (Adopt the Pull Request filter above)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 54 + }, + "id": 54, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.created_date AS pr_created_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, pr_created_date, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (review_created_date - pr_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR pickup time from pr_comments' FROM _pr_comment_ranks WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT id, pr_pickup_time AS 'PR pickup time from project_pr_metrics' /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "refId": "B", + "select": [ + [ + { + "params": [ + "blueprint_id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprint_labels", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4-2. Check if PR pickup time in project_pr_metrics is correct (Adopt the Pull Request filter above)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 58 + }, + "id": 55, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.merged_date AS pr_merged_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank_asc FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (pr_merged_date - review_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR review time from pr_comments' FROM _pr_comment_ranks WHERE comment_rank_asc = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT id, pr_review_time AS 'PR review time from project_pr_metrics' /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "refId": "B", + "select": [ + [ + { + "params": [ + "blueprint_id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprint_labels", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4-3. Check if PR review time in project_pr_metrics is correct (Adopt the Pull Request filter above)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 58 + }, + "id": 56, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR deploy time from cicd_deployment_commits' FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT id, pr_deploy_time AS 'PR deploy time from project_pr_metrics' FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "refId": "B", + "select": [ + [ + { + "params": [ + "blueprint_id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprint_labels", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4-4. Check if PR deploy time in project_pr_metrics is correct (Adopt the Pull Request filter above)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 62 + }, + "id": 57, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS 'PR cycle time from lower-level metrics', ppm.\"pr_cycle_time\" AS 'PR cycle time from project_pr_metrics' FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND pr.id = '$pr_id'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4-5. Check if PR cycle time in project_pr_metrics is correct (Adopt the Pull Request filter above)", + "type": "gauge" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "ranks" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "from": 0, + "result": { + "color": "green", + "index": 0 + }, + "to": 0.5 + }, + "type": "range" + }, + { + "options": { + "from": 0.5, + "result": { + "color": "orange", + "index": 1 + }, + "to": 1 + }, + "type": "range" + } + ] + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "month" + }, + "properties": [ + { + "id": "custom.filterable", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 66 + }, + "id": 70, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(cdc.finished_date, '%y/%m') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 5 - check the median change lead time for each month in Figure 4 (Compare the change_lead_time with the max ranks in GREEN before the first occurrence of ORANGE in each month)", + "type": "table" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 74 + }, + "id": 26, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Check \"Change Failure Rate\" & \"${title_value}\"", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 66, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See the definition and calculation logic of [${title_value}](https://devlake.apache.org/docs/Metrics/MTTR)\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, BitBucket Pipelines, or Webhook, etc. \n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 79 + }, + "id": 67, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See the definition and calculation logic of [Change Failure Rate](https://devlake.apache.org/docs/Metrics/CFR)\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, BitBucket Pipelines, or Webhook, etc. \n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "INCIDENT": { + "color": "green", + "index": 0 + }, + "This project is selected": { + "color": "green", + "index": 1 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 0, + "y": 83 + }, + "id": 31, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, i.type, COUNT(1) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id AND pm.\"table\" = 'boards' WHERE pm.project_name IN ($project) AND /* \tand i.type = 'INCIDENT' */ $__timeFilter(i.created_date) GROUP BY pm.project_name, select_status, i._raw_data_table, i.type", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 1. All types of issues in table.issues (rows with 2 green columns will be used to construct project_incident_deployment_relationships)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 83 + }, + "id": 42, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"No data\" END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 5 - ${title_value}", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "INCIDENT": { + "color": "green", + "index": 0 + }, + "This project is selected": { + "color": "green", + "index": 1 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 89 + }, + "id": 14, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, COUNT(1) AS issue_count FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name IN ($project) AND $__timeFilter(i.created_date) GROUP BY pm.project_name, select_status, i._raw_data_table", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 2. Number of Incidents in the selected project(s)", + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 15, + "w": 16, + "x": 0, + "y": 93 + }, + "id": 61, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "\n\nIn this case:\n\n- Deployment-1 maps to Incident-1\n- Deployment-3 maps to Incident-2 and Incident-3\n- Deployment-2,4,5 doesn't map to any Incident", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Deployment - Incident Mapping and CFR calculation logic", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "median_time_to_resolve_in_hour" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 93 + }, + "id": 46, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.created_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 6 - ${title_value}", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 103 + }, + "id": 44, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERInvalid dora reportSTRING_LITERAL_11_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 7 - Change Failure Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 0 + }, + "INCIDENT": { + "color": "red", + "index": 1 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 8, + "x": 0, + "y": 108 + }, + "id": 58, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project)) SELECT deployment_id AS id, 'DEPLOYMENT' AS type, finished_date AS time FROM _deployments UNION SELECT issue_id AS id, 'INCIDENT' AS type, created_date AS time FROM _incidents ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 3. The sequence of DEPLOYMENTS and INCIDENTS", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "FALSE": { + "color": "green", + "index": 1 + }, + "TRUE": { + "color": "red", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 8, + "x": 8, + "y": 108 + }, + "id": 59, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT cdc.cicd_deployment_id AS deployment_id /* in CFR we use deployment_commit_id as the deployment_id in a specific repo */, cdc.finished_date, pim.id AS incident_id, CASE WHEN NOT pim.id IS NULL THEN 'TRUE' ELSE 'FALSE' END AS has_failure FROM cicd_deployment_commits AS cdc LEFT JOIN project_incident_deployment_relationships AS pim ON cdc.cicd_deployment_id = pim.deployment_id LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND $__timeFilter(cdc.finished_date) ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Step 4. Check if the DEPLOYMENT and INCIDENT mapping results are consistent with them in step 3 and figure 7&8", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "change_failure_rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 111 + }, + "id": 48, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Figure 8 - Change Failure Rate", + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "select concat(Url, '--', id) from pull_requests", + "hide": 0, + "includeAll": false, + "label": "Pull Request Url", + "multi": false, + "name": "pr_id", + "options": [], + "query": "select concat(Url, '--', id) from pull_requests", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Validation (PostgreSQL)", + "uid": "KGkUnV-Vz-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json b/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json new file mode 100644 index 00000000000..23a20909db4 --- /dev/null +++ b/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json @@ -0,0 +1,758 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 15, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Go Back", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora?orgId=1" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "links": [], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard shows the details about [Change Failure Rate](https://devlake.apache.org/docs/Metrics/CFR) in DORA.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 2 + }, + "id": 86, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Change Failure Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "FALSE": { + "color": "red", + "index": 1 + }, + "TRUE": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "incident_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 134 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "deployment_finished_date" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + }, + { + "id": "custom.width", + "value": 271 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "created_date" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields[\"metric_hidden\"]}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 17, + "w": 20, + "x": 4, + "y": 2 + }, + "id": 85, + "links": [ + { + "title": "docs", + "url": "https://devlake.apache.org/docs/Metrics/CFR/#how-is-it-calculated" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. Deployments that have caused incidents(failure)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 4, + "x": 0, + "y": 8 + }, + "id": 84, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 0 + }, + "INCIDENT": { + "color": "red", + "index": 1 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 58, + "links": [ + { + "targetBlank": true, + "title": "See detail logic", + "url": "https://devlake.apache.org/docs/Metrics/CFR#how-is-it-calculated" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project) AND $__timeFilter(i.created_date)) SELECT finished_date AS 'Time (Ascending)', deployment_id AS 'Entity ID', 'DEPLOYMENT' AS 'Entity Type (Deployment/Incident)' FROM _deployments UNION SELECT created_date AS 'Time (Ascending)', issue_id AS 'Entity ID', 'INCIDENT' AS 'Entity Type (Deployment/Incident)' FROM _incidents ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Deployment and incidents timeline", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Details - Change Failure Rate (PostgreSQL)", + "uid": "Change-failure-rate-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json b/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json new file mode 100644 index 00000000000..cabce1b2e98 --- /dev/null +++ b/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json @@ -0,0 +1,1335 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 42, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Go Back", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora?orgId=1" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard shows the details about [deployment frequency](https://devlake.apache.org/docs/Metrics/DeploymentFrequency) in DORA.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": true + }, + "links": [], + "mappings": [ + { + "options": { + "DEPLOYMENT": { + "color": "green", + "index": 1 + }, + "PRODUCTION": { + "color": "green", + "index": 0 + }, + "SUCCESS": { + "color": "green", + "index": 2 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "deployment_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 256 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "project_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 122 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "result" + }, + "properties": [ + { + "id": "custom.width", + "value": 116 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "environment" + }, + "properties": [ + { + "id": "custom.width", + "value": 118 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "deployment_commit_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 295 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "display_title" + }, + "properties": [ + { + "id": "custom.width", + "value": 530 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "custom.width", + "value": 498 + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields[\"metric_hidden\"]}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 18, + "x": 0, + "y": 2 + }, + "id": 79, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.display_title, cdc.url, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, CASE WHEN display_title = '' THEN 'N/A' ELSE display_title END AS display_title, url, url AS metric_hidden, result /* a deployment may have multiple deployment_commits */ /* id as deployment_commit_id, */, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date) ORDER BY finished_date DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Deployment list in the selected project(s)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "day_range" + }, + "properties": [ + { + "id": "custom.width", + "value": 210 + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 6, + "x": 18, + "y": 2 + }, + "id": 75, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_days AS (/* Construct the last few calendar days within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT TO_CHAR(last_few_calendar_days.day, '%Y-%m-%d') AS day, COALESCE(COUNT(d.finished_date), 0) AS successful_deployments_to_prod FROM last_few_calendar_days LEFT JOIN _deployments AS d ON last_few_calendar_days.day = CAST(d.finished_date AS DATE) GROUP BY 1 ORDER BY 1 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. Daily deployments in the selected project(s)", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 7 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 74, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%m/%dSTRING_LITERAL_5_PLACEHOLDER - STRING_LITERAL_6_PLACEHOLDER%m/%dSTRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Weekly deployments in the selected project(s)", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 29 + }, + "id": 76, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xField": "month", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%y/%mSTRING_LITERAL_5_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4. Per month deployments in the selected project(s)", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 180 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 29 + }, + "id": 77, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%y/%mSTRING_LITERAL_5_PLACEHOLDER ~ STRING_LITERAL_6_PLACEHOLDER%y/%mSTRING_LITERAL_7_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5. Per six months deployments in the selected project(s)", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 37 + }, + "id": 93, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# 2023 Benchmarks\n- Elite: >= 5 days per week\n- High: >= 1 day per week\n- Medium: >= 1 day per month\n- Low: < 1 day per month\n# 2021 Benchmarks\n- Elite: >= 5 days per week\n- High: >= 1 day per month\n- Medium: >= 1 day per six months\n- Low: < 1 day per six months", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "title": "DORA Report", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#a6a6a6", + "value": null + } + ] + }, + "unit": "days per week" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 37 + }, + "id": 90, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "6. Median deployments days at the weekly level", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#a6a6a6", + "value": null + } + ] + }, + "unit": "days per month" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 12, + "y": 37 + }, + "id": 91, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "7. Median deployments days at monthly level", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#a6a6a6", + "value": null + } + ] + }, + "unit": "days per six months" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 37 + }, + "id": 92, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "8. Median deployments days at per six months level", + "type": "stat" + } + ], + "schemaVersion": 39, + "tags": [ + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Details - Deployment Frequency (PostgreSQL)", + "uid": "Deployment-frequency-pg", + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json new file mode 100644 index 00000000000..acd10b5b77d --- /dev/null +++ b/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json @@ -0,0 +1,478 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 45, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Go Back ", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora?orgId=1" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard shows the details about [Failed Deployment Recovery Time\n](https://devlake.apache.org/docs/Metrics/MTTR) in DORA.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 5, + "x": 0, + "y": 2 + }, + "id": 87, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN NOT median_recovery_time IS NULL THEN CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) ELSE \"No data\" END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "1. Failed Deployment Recovery Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": true + }, + "mappings": [ + { + "options": { + "FALSE": { + "color": "red", + "index": 1 + }, + "TRUE": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "deployment_finished_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 174 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "incident_resolution_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 208 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_deployment_recovery_time" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "green", + "value": 24 + }, + { + "color": "#EAB839", + "value": 168 + }, + { + "color": "red", + "value": 720 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 19, + "x": 5, + "y": 2 + }, + "id": 80, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": [ + "failed_deployment_recovery_time" + ], + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "deployment_finished_date" + } + ] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT fd.deployment_id AS \"deployment_id\", fd.deployment_finished_date, i.id AS incident_caused_by_deployment /* date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month, */, i.title AS incident_title, i.url AS incident_url, i.url AS \"metric_hidden\", i.resolution_date AS incident_resolution_date /* i.created_date as incident_create_date, */, (EXTRACT(EPOCH FROM (i.resolution_date - fd.deployment_finished_date))/3600) AS \"failed_deployment_recovery_time\" FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)) SELECT * FROM _incidents_for_deployments WHERE NOT incident_resolution_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Deployments and Incidents Details", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 5, + "x": 0, + "y": 12 + }, + "id": 88, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "2. The number of incidents caused by deployments", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Details - Failed Deployment Recovery Time (PostgreSQL)", + "uid": "Failed-deployment-recovery-time-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json b/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json new file mode 100644 index 00000000000..2337bf19517 --- /dev/null +++ b/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json @@ -0,0 +1,967 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Go Back", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora?orgId=1" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- This dashboard shows the details about [Lead Time for Changes](https://devlake.apache.org/docs/Metrics/LeadTimeForChanges) in DORA.\n- It displays the statistics for pull requests **deployed** within the selected time range.", + "mode": "markdown" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 2 + }, + "id": 75, + "links": [ + { + "targetBlank": true, + "title": "PR Cycle Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCycleTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS 'PR Cycle Time(h)' FROM _prs", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Average PR Cycle Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 8, + "y": 2 + }, + "id": 71, + "links": [ + { + "targetBlank": true, + "title": "PR Coding Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCodingTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS 'Coding Time(h)' FROM _prs", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Average PR Coding Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 12, + "y": 2 + }, + "id": 72, + "links": [ + { + "targetBlank": true, + "title": "PR Pickup Time", + "url": "https://devlake.apache.org/docs/Metrics/PRPickupTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS 'Pickup Time(h)' FROM _prs", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.2 Average PR Pickup Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 16, + "y": 2 + }, + "id": 73, + "links": [ + { + "targetBlank": true, + "title": "PR Review Time", + "url": "https://devlake.apache.org/docs/Metrics/PRReviewTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS 'Review Time(h)' FROM _prs", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.3 Average PR Review Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 20, + "y": 2 + }, + "id": 74, + "links": [ + { + "targetBlank": true, + "title": "PR Deploy Time", + "url": "https://devlake.apache.org/docs/Metrics/PRDeployTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS 'PR Deploy Time(h)' FROM _prs", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.4 Average PR Deploy Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Lead time for changes = median(PR deployed date - PR's first commit's authored date)", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "green", + "value": 24 + }, + { + "color": "orange", + "value": 168 + }, + { + "color": "red", + "value": 720 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "change_lead_time" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR url" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields[\"metric_hidden\"]}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR title" + }, + "properties": [ + { + "id": "custom.width", + "value": 674 + } + ] + } + ] + }, + "gridPos": { + "h": 15, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 70, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": true, + "enablePagination": false, + "fields": "", + "reducer": [ + "count" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, pr.title, pr.url, pr.created_date, ppm.pr_coding_time, ppm.pr_pickup_time, ppm.pr_review_time, ppm.pr_deploy_time, ppm.first_commit_sha, prc.commit_authored_date, cdc.cicd_deployment_id, cdc.name, cdc.finished_date, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id JOIN pull_request_commits AS prc ON prc.commit_sha = ppm.first_commit_sha WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)) SELECT title AS \"PR title\" /* id as \"PR id\", */, url AS \"PR url\", url AS metric_hidden, first_commit_sha AS \"First commit sha\" /* created_date as \"PR created_date\", */, commit_authored_date AS \"First commit authored date\", cicd_deployment_id AS \"Deployment id\", finished_date AS \"Deployment finished_date\" /* name as \"Deployment name\", */, CAST(pr_coding_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_coding_time\", CAST(pr_pickup_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_pickup_time\", CAST(pr_review_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_review_time\", CAST(pr_deploy_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_deploy_time\", CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time FROM _pr_stats", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. PR Details", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "select dora_report from dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "select dora_report from dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Details - Lead Time for Changes (PostgreSQL)", + "uid": "Lead-time-for-changes-pg", + "version": 7, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json b/grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json new file mode 100644 index 00000000000..30688cd3392 --- /dev/null +++ b/grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json @@ -0,0 +1,465 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 10, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Go Back", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora?orgId=1" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 63, + "links": [], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "This dashboard shows the details about [Time to restore service\n](https://devlake.apache.org/docs/Metrics/MTTR) in DORA.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 5, + "x": 0, + "y": 2 + }, + "id": 87, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5) SELECT CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS median_time_to_resolve_in_hours FROM _median_mttr", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "1. Median Time to Restore Service", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgba(255, 255, 255, 1)", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "FALSE": { + "color": "red", + "index": 1 + }, + "TRUE": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "green", + "value": 24 + }, + { + "color": "yellow", + "value": 168 + }, + { + "color": "red", + "value": 720 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields[\"metric_hidden\"]}" + } + ] + }, + { + "id": "custom.width", + "value": 432 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "metric_hidden" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 819 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "time_to_restore_service" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + } + ] + }, + "gridPos": { + "h": 19, + "w": 19, + "x": 5, + "y": 2 + }, + "id": 86, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": [ + "lead_time_minutes" + ], + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "resolution_date" + } + ] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id AS \"incident_id\", i.title, i.url, i.url AS \"metric_hidden\", i.resolution_date /* i.created_date, */, CAST(CAST(lead_time_minutes AS NUMERIC) / NULLIF(60, 0) AS BIGINT) AS time_to_restore_service FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name IN ($project) AND $__timeFilter(i.resolution_date)) SELECT * FROM _incidents ORDER BY resolution_date DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Time to restore service details", + "transparent": true, + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 5, + "x": 0, + "y": 11 + }, + "id": 88, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(id) AS \"incident count\" FROM _incidents", + "refId": "D", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "2. Incident Count", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "DORA" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "DORA Details - Time to Restore Service (PostgreSQL)", + "uid": "Time-to-restore-service-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json b/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json new file mode 100644 index 00000000000..a8efd2838f0 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json @@ -0,0 +1,250 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 1, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Back to previous page", + "tooltip": "", + "type": "link", + "url": "/grafana/d/SupYz7c7z/demo-how-fast-do-we-respond-to-customer-requirements?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "1. Compare the average time spent in each state of a requirement in the last 2 months.\n2. The requirements being calculated are the requirements delivered in the last 2 months.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "Days", + "axisPlacement": "auto", + "axisSoftMax": 8, + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 98, + "options": { + "barWidth": 0.6, + "groupWidth": 0.5, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "vertical", + "showValue": "always", + "text": { + "valueSize": 16 + }, + "tooltip": { + "mode": "multi" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS 'Assignee', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Average Requirement Lead Time by Assignee (day)", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 100, + "options": { + "showHeader": true + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS 'Assignee', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "type": "table" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 102, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Average Requirement Lead Time By Assignee (PostgreSQL)", + "uid": "q27fk7cnk-pg", + "version": 6 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json b/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json new file mode 100644 index 00000000000..3ea056c1ed5 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json @@ -0,0 +1,257 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 5, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Back to previous page", + "tooltip": "", + "type": "link", + "url": "/grafana/d/ddREk75nk/demo-is-this-month-more-productive-than-last?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "The number of commits from different commit authors.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 50, + "options": { + "barWidth": 0.6, + "groupWidth": 0.5, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 14 + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Commit Count by Author [Top 20]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 38, + "options": { + "showHeader": true + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "type": "table" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 52, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Commit Count by Author (PostgreSQL)", + "uid": "F0iYknc7z-pg", + "version": 3 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoDetailedBugInfo.json b/grafana/dashboards/postgresql/DemoDetailedBugInfo.json new file mode 100644 index 00000000000..50479310dd4 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoDetailedBugInfo.json @@ -0,0 +1,315 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 2, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Back to previous page", + "tooltip": "", + "type": "link", + "url": "/grafana/d/G4DEk75nz/demo-was-our-quality-improved-or-not?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 32, + "options": { + "barWidth": 0.3, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 16 + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH bugs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1 ORDER BY 1 DESC NULLS LAST) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count AS 'Bug Count over Month' FROM bugs ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Bug count over Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 405 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "summary" + }, + "properties": [ + { + "id": "custom.width", + "value": 538 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "description" + }, + "properties": [ + { + "id": "custom.width", + "value": 753 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "created_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 160 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "status" + }, + "properties": [ + { + "id": "custom.width", + "value": 284 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 30, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT title, description, CASE WHEN assignee_id = '' THEN '-' ELSE assignee_name END AS assignee, status, created_date, url FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) ORDER BY created_date DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "type": "table" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 34, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Detailed Bug Info (PostgreSQL)", + "uid": "s48Lzn5nz-pg", + "version": 8 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoHomepage.json b/grafana/dashboards/postgresql/DemoHomepage.json new file mode 100644 index 00000000000..55de971ebf7 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoHomepage.json @@ -0,0 +1,191 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 10, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "datasource": null, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 2, + "interval": null, + "links": [], + "options": { + "content": "\n
\n

How fast do we respond to customer requirements?

\n
\n
", + "mode": "html" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": false, + "rawSql": "SELECT create_time AS \"time\", progress FROM ca_analysis WHERE $__timeFilter(create_time) ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "type": "text" + }, + { + "cacheTimeout": null, + "datasource": null, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 3, + "interval": null, + "links": [], + "options": { + "content": "\n
\n

Was our quality improved or not?

\n
\n
", + "mode": "html" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": false, + "rawSql": "SELECT create_time AS \"time\", progress FROM ca_analysis WHERE $__timeFilter(create_time) ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "type": "text" + }, + { + "cacheTimeout": null, + "datasource": null, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 4, + "interval": null, + "links": [], + "options": { + "content": "\n
\n

Is this month more productive than last?

\n
\n
", + "mode": "html" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": false, + "rawSql": "SELECT create_time AS \"time\", progress FROM ca_analysis WHERE $__timeFilter(create_time) ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Homepage (PostgreSQL)", + "uid": "0Rjxknc7z-pg", + "version": 2 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json b/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json new file mode 100644 index 00000000000..864d7a57d46 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json @@ -0,0 +1,223 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 11, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 17, + "interval": "", + "links": [ + { + "title": "Drill down by Assignee", + "url": "/grafana/d/q27fk7cnk/demo-average-requirement-lead-time-by-assignee?orgId=1" + } + ], + "options": { + "barWidth": 0.25, + "groupWidth": 0.74, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 18 + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time_days FROM issues AS i WHERE type = 'REQUIREMENT' AND $__timeFilter(resolution_date) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, lead_time_days AS 'Average Requirement Lead Time (day)' FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Average Requirement Lead Time over time (day)", + "type": "barchart" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 5, + "x": 0, + "y": 9 + }, + "id": 103, + "options": { + "content": "
\n\n[Drill down by assignee](/d/q27fk7cnk/demo-average-requirement-lead-time-by-assignee?orgId=1)\n ", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 101, + "options": { + "content": "
\n \"Merico\"\n

MARI Guide - Requirement Lead Time

\n
\n\nSection | Description\n:----------------- | :-------------\nMetric Definition | Total duration of requirements from proposal to delivery. It can be divided by flow status in the practice domain or project management system to count the time share of each phase and help locate the links that drag out the requirement delivery cycle.\nMetric Value | The Requirement Lead Time reflects the rapid responsiveness of the R&D team.
In theory, the faster you can deliver value to customers, the better, but other aspects such as whether the delivered value meets customer expectations, requirement throughput, and delivery quality must be considered together. Fast delivery does not necessarily equate to good R&D practices.\n\n***\n#### *M (Measure)*\n1. Count the average or 80th percentile requirement lead time for different times.\n2. Counts the average or 80th percentile requirement lead time for different projects.
\n3. Count the length of time that requirements stay in different practice domains (requirements analysis, design, development, testing, release) or in different states.\n\n##### *A (Analyze)*\n1. Compare the requirement delivery speed of different projects to find the fastest and slowest delivering projects.\n2. Analyze the trend of the average requirement lead time within each cycle, make a vertical comparison, and locate the key points such as maximum value, minimum value, continuous up cycle, and continuous down cycle.\n3. Analyze the trend of the delivery cycle of 80% of the requirements within each cycle, make a longitudinal comparison, and locate the key points such as maximum value, minimum value, continuous up cycle, and continuous down cycle.

\nWhy choose the 80% quantile instead of using the average?
\nThe point of statistics is to make predictions with real and valid data to support better decisions, while the mean and median cannot have the role of supporting predictions.
\nTypically, the mean and 80% quantile statistics will appear twice as far apart, and the 80% and 99% quantile tend to be approximately twice as related.
\nTherefore, the 80% quantile is a good balance point for prediction.\n
\n4. Analysis compares the length of time requirement stays in different practice domains or different states to identify the most time-consuming links and find the key bottlenecks that affect overall delivery speed.\n5. Requirement lead time is correlated with requirement throughput to identify whether the requirement delivery trend is healthy or not.\n - Healthy trend: requirement lead time is shortened and requirement throughput is increased.\n - Unhealthy trend: longer requirement lead time and lower requirement throughput.\n\n\n##### *R (Review)*\nBased on the analysis results, focus on a few key points and use Ishikawa diagram (fishbone diagram) or Root Cause Analysis (RCA) to conduct root cause analysis, research and review. For example, if the requirement delivery cycle becomes longer in several consecutive statistical cycles, it is necessary to further investigate the length of stay of requirements in different phases and find the longest phase for root cause analysis.\n\n1. The requirements phase takes too long: unclear requirements, frequent changes, overly granular requirements, requirements priorities not clearly defined, insufficient resources or experience of requirements analysts or product managers?\n2. The design phase takes too long: unclear requirement documents, insufficient resources or experience of R&D leaders or architects?\n3. The development phase takes too long: unclear design documents, uneven task distribution, high stream load (parallel tasks), too much technical debt, too many bugs, insufficient resources or experience of developers?\n4. The testing phase takes too long: unclear requirements documentation, poor code quality, few automated tests, insufficient resources or experience of testers?\n5. The release phase takes too long: too long build or deployment time, insufficient resources or experience of operation and maintenance staff?\n\n##### *I (Improve)*\nBased on the review results, focus on the key root causes, and give targeted improvement measures in terms of norms, processes, tools and behaviors, etc., with clear improvement targets, improvement measures, verification cycles and responsible persons.\n\nThe following are the improvement ideas for reference:\n\n1. Communicate with customers or business parties to clarify requirements, reasonably disassemble requirements and define priorities, and invite business parties, R&D leaders and testing leaders to review requirements.\n2. Invite product managers, R&D personnel, and test leaders to conduct design reviews.\n3. Reduce requirement or task granularity, distribute tasks evenly, reduce flow load, increase unit testing, and solve technical debt and code problems in time to reduce the number of bugs and rework.\n4. Test left, develop self-test, code review, increase automated testing, and improve continuous integration capabilities.\n5. Automate deployment, shorten build time, and improve continuous delivery.\n6. Reasonable resource allocation and necessary training for each job holder.\n7. The improvement results should also be quantifiable to facilitate continuous metrics and tracking of improvement effects.", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 105, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-How fast do we respond to customer requirements? (PostgreSQL)", + "uid": "SupYz7c7z-pg", + "version": 4 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json b/grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json new file mode 100644 index 00000000000..9da3d2e0876 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json @@ -0,0 +1,253 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 12, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "links": [ + { + "targetBlank": true, + "title": "Break down by user1", + "url": "/grafana/d/cHnTA7K7z/is-this-month-more-productive-than-last-by-users?orgId=1" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "Commit Count" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 36, + "links": [ + { + "targetBlank": false, + "title": "Drill down by Commit Author", + "url": "/d/F0iYknc7z/demo-commit-count-by-author?orgId=1" + } + ], + "options": { + "barWidth": 0.25, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": {}, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _commits AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS commit_count FROM commits WHERE NOT message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, commit_count AS \"Commit Count\" FROM _commits ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Commit Count over Time", + "type": "barchart" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 5, + "x": 0, + "y": 9 + }, + "id": 40, + "options": { + "content": "
\n\n[Drill down by commit author](/d/F0iYknc7z/demo-commit-count-by-author?orgId=1)\n ", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 38, + "options": { + "content": "
\n \"Merico\"\n

MARI Guide - Commit Count

\n
\n\nSection | Description\n:------------------ | :-------------\nMetric Definition | The total number of commits.\nMetric Value | The number of commits reflects the frequency of code commits and encourages multiple small step commits per day. While paying attention to the number of times, you should also observe the number of lines of code per commit.\n\n***\n\n#### *M (Measure)*\nThe number of commits can be counted in time, project, team or individual dimensions, and the above 4 dimensions can be cross-tabulated, e.g., the number of commits per week for a project, the average number of commits per person per day for a team.\n\n##### *A (Analyze)*\n1. By project dimension, analyze and compare the number of code commits of different projects horizontally, and locate the projects with the maximum, minimum and lower than average values for research.\n2. By team dimension, analyze and compare the number of code commits of different teams horizontally, and locate the teams with the maximum, minimum, and lower than average values for research.\n3. By individual dimension, analyze and compare the number of code commits of different personnel horizontally, and locate the individuals with the maximum, minimum, and lower than average values for research.\n4. Analyze the trend of the number of code commits in each cycle, compare vertically, and locate the maximum, minimum, and continuously increasing or decreasing cycles for research.\n\n##### *R (Review)*\nBased on the results of metrics analysis, use Ishikawa diagram (fishbone diagram) or root cause analysis (RCA) to analyze, investigate and review the root causes of projects/teams/individuals with low (e.g. lower than average) code submission times in a cycle:\n1. too many complicated requirements with large granularity (estimated workload)?\n2. uneven and full task distribution?\n3. no specification for small step submission?\n\n##### *I (Improve)*\nBased on the review results, focus on the key root causes, give targeted improvement measures in terms of specification, process, tools, behavior, etc., and clarify the improvement target, improvement measures, verification cycle and responsible person.\nTo address the root causes of low code submission in the cycle, the following improvement measures were taken: 1.\n1. reduce the granularity of requirements.\n2. distribute tasks evenly so that each person has a full workload each day.\n3. set small step commit specifications, such as an average number of commits per person per day greater than 1, an average number of lines per commit less than 100, etc.", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 42, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Is this month more productive than last? (PostgreSQL)", + "uid": "ddREk75nk-pg", + "version": 3 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json b/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json new file mode 100644 index 00000000000..598a0318003 --- /dev/null +++ b/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json @@ -0,0 +1,220 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 6, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/0Rjxknc7z/demo-homepage?orgId=1" + } + ], + "panels": [ + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 35, + "links": [ + { + "targetBlank": false, + "title": "See Detailed Bug Info", + "url": "/grafana/d/s48Lzn5nz/demo-detailed-bug-info?orgId=1" + } + ], + "options": { + "barWidth": 0.3, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "always", + "text": { + "valueSize": 14 + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> 0) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count_per_1k_loc AS 'Bug Count per 1000 Lines of Code' FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Bug Count per 1k Lines of Code", + "type": "barchart" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 5, + "x": 0, + "y": 8 + }, + "id": 37, + "options": { + "content": "
\n\n[See Detailed Bug Info](/d/s48Lzn5nz/demo-detailed-bug-info?orgId=1)\n ", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 32, + "options": { + "content": "
\n \"Merico\"\n

MARI Guide - Bug Count per 1k Lines of Code

\n
\n\nSection | Description\n:----------------- | :-------------\nMetric Definition | The ratio of the number of bugs found to the corresponding amount of code or code changes, to characterize the density of overall bugs, including bugs found in testing and online. For example, bug count per 1k lines of code, bug count per 1k code equivalent.\nMetric Value | The bug rate, as a quality indicator, represents the density of bugs, and is one of the important indicators used to assess the quality of software products and testing quality. Usually, the cost of fixing detected bugs is higher in the later stage of the software development life cycle, and this metric is valuable for analyzing and evaluating both online quality and bug fixing cost.\n\n***\n#### *M (Measure)*\n1. Bug count per 1k lines of code by project.\n2. Trends in 'Bug count per 1k lines of code' over time.\n3. Measure historical data to establish year-over-year and historical baseline reference values for 'Bug count per 1k lines of code'.\n\n##### *A (Analyze)*\n1. Year-over-year analysis: The bug rate of similar projects in the same period is compared and analyzed, and the improvement effect of product quality is observed through the rise and fall of the data.\n2. Circumferential analysis: Analyze the bug rate of projects in the recent year, analyze the change of online bug rate according to the time axis, and compare with the historical baseline at the same time to give a judgment analysis of the rise and fall of indicators.\n3. Trend analysis: analyze the trend of bug rate over time (days, weeks, months), judge the trend rise, and evaluate whether the stable cycle of product quality is reasonable by observing changes such as trend slowing-down and smoothing.\n4. Horizontal analysis: Compare the bug rate of multiple projects as a reference to evaluate the quality of software products online.\n5. Classification analysis: Classify and analyze the types of bugs, severity levels, and modules they belong to, and identify the key issues that show aggregated distribution.\n\n##### *R (Review)*\nFor the high severity level of online bugs should be a complete review, according to the timeline, role dimensions, the sequence of events on the root cause of bugs to dig, locate the key issues.\nAccording to the quantitative conclusions drawn from the analysis, further data drilling and root cause mining can be organized for bugs in several dimensions, including whether they are missed, the module they belong to, the cause, the occurrence cycle, and the resolution.\n1. bug escape rate: derived from [number of online bugs / (number of online bugs + number of bugs found in test)], this indicator can be compared with historical data, if the data exceeds the acceptable range of history and testing department, then it is necessary to conduct leak analysis. If the data exceeds the acceptable interval of history and testing department, it is necessary to analyze the missed test and confirm whether the use case is missed or not covered, so as to strengthen the use case design and management.\n2. defective module: The defective module can be located to the key module where the problem is concentrated, and targeted improvement measures are required for each link from requirements, design, development to testing, and typical problems are located for the bugive module to establish targeted measures.\n3. bug generation causes: through the cause analysis of bugs, similar bugs bugs can be put together, so that bugs belonging to the same category and accounting for a high percentage of bugs will be highlighted, so that it is easy to take out the bugs with more concentrated causes and jointly discuss the next improvement measures to precisely reduce the number of similar bugs.\n4. bug occurrence cycle: analyze the bug occurrence cycle, determine whether the users use the system frequently, whether the system has been updated or optimized, whether the system has been refactored, etc., which will cause a long and short bug occurrence cycle after the launch, through the analysis of the bug cycle length, to draw some valuable conclusions about the stability of the system.\n5. bug resolution: statistics on the resolution of bugs, which bugs are not reproduced, which are temporarily handled, and which are in need of continuous improvement. For the temporary resolution of bugs, analyze whether it will cause another bug elsewhere in the system, whether the user can receive the temporary resolution, what development and testing need to focus on similar bugs, and whether similar bugs need to be tested elsewhere for horizontal expansion. Those that require continuous improvement need to be further tracked by testing until the problem is resolved.\n\n##### *I (Improve)*\nThrough root cause mining, starting from the key bugs, the key problems of each link to locate, in accordance with the principle that the later the bugs are found, the higher the cost and complexity of the solution, in addition to the test design and implementation to start improving (refer to the improvement links of the number of bugs on the line), more should start the construction of quality from the upstream of the software engineering stage, to achieve the forward movement of the bug discovery stage, such as\n1. optimizing static scan rules according to the type, number and severity level of static scan problems to reduce false positives and expose as many serious problems as possible (quality over quantity).\n2. Define the requirement of resolution ratio for different severity level problems to control the backlog of serious problems.\n3. Establish code review system, strategy, and encourage the promotion of code review implementation.\n4. Establish unit test coverage ratio or unit test coverage condition requirements, e.g. functions with circle complexity greater than 10 shall be covered by unit tests.\nImplement improvement measures and clarify the improvement target, improvement measures, verification cycle and responsible person. Do a new round of MARI (Measure, Analysis, Review, and Verification) for the improvement effect to quantify the improvement effect.", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 39, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Demo-Was our quality improved or not? (PostgreSQL)", + "uid": "G4DEk75nz-pg", + "version": 4 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DeveloperProductivityHours.json b/grafana/dashboards/postgresql/DeveloperProductivityHours.json new file mode 100644 index 00000000000..37253acc4f1 --- /dev/null +++ b/grafana/dashboards/postgresql/DeveloperProductivityHours.json @@ -0,0 +1,267 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Chat and completion events by hour of day", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "bars", + "fillOpacity": 80, + "lineWidth": 1, + "stacking": { + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT LPAD(CAST(hr AS TEXT), 2, '0') AS 'Hour', SUM(chat) AS 'Chat Events', SUM(comp) AS 'Completion Events' FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hr, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hr, 0 AS chat, COUNT(*) AS comp FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS t GROUP BY hr ORDER BY hr NULLS FIRST", + "refId": "A" + } + ], + "title": "AI Activity by Hour of Day (UTC)", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Average prompt complexity and response richness by hour", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS 'Hour', ROUND(AVG(prompt_length)) AS 'Avg Prompt Length', ROUND(AVG(response_length)) AS 'Avg Response Length' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", + "refId": "A" + } + ], + "title": "Prompt & Response Length by Hour", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Steering and spec mode usage concentration by hour", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "bars", + "fillOpacity": 80, + "lineWidth": 1, + "stacking": { + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS 'Hour', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode', SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS 'Plain Chat' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", + "refId": "A" + } + ], + "title": "Feature Usage by Hour", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Day-of-week activity pattern", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "bars", + "fillOpacity": 80, + "lineWidth": 1, + "stacking": { + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) WHEN 1 THEN 'Sun' WHEN 2 THEN 'Mon' WHEN 3 THEN 'Tue' WHEN 4 THEN 'Wed' WHEN 5 THEN 'Thu' WHEN 6 THEN 'Fri' WHEN 7 THEN 'Sat' END AS 'Day', SUM(chat) AS 'Chat Events', SUM(comp) AS 'Completions' FROM (SELECT timestamp, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY timestamp UNION ALL SELECT timestamp, 0, COUNT(*) FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY timestamp) AS t GROUP BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) ORDER BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) NULLS FIRST", + "refId": "A" + } + ], + "title": "AI Activity by Day of Week", + "type": "barchart" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "productivity", + "hours" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Developer AI Productivity Hours (PostgreSQL)", + "uid": "kiro_productivity_hours-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/EngineeringOverview.json b/grafana/dashboards/postgresql/EngineeringOverview.json new file mode 100644 index 00000000000..88e7c89de02 --- /dev/null +++ b/grafana/dashboards/postgresql/EngineeringOverview.json @@ -0,0 +1,2386 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 40, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 32, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard is to overview the Git and project management metrics.\n- Data Sources Required:\n - One of the Git tools, e.g. [GitHub](https://devlake.apache.org/docs/Configuration/GitHub), [GitLab](https://devlake.apache.org/docs/Configuration/GitLab), [Bitbucket](https://devlake.apache.org/docs/Configuration/BitBucket) or [Azure DevOps](https://devlake.apache.org/docs/Configuration/AzureDevOps)\n - One of the issue tracking tools, e.g. [Jira](https://devlake.apache.org/docs/Configuration/Jira)", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.priority = ANY(ARRAY[${priority}]::text[]) AND i.type = 'BUG' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Critical Defects Identified [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 3 + }, + "id": 22, + "options": { + "barRadius": 0, + "barWidth": 0.46, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS defect_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.priority = ANY(ARRAY[${priority}]::text[]) AND i.type = 'BUG' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, defect_count FROM _issues ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Critical Defects Identified over Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 11 + }, + "id": 10, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND i.resolution_date BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 11 + }, + "id": 23, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = \"DONE\" AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Requirement Lead Time in Days [Of Issues Resolved per Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "green", + "value": 15 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 19 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT author_name) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Developers [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 19 + }, + "id": 24, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _developers AS (SELECT CAST(c.authored_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT author_name) AS developer_count FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(c.authored_date) AND c.authored_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, developer_count FROM _developers ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Developers", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "#EAB839", + "value": 60 + }, + { + "color": "green", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 27 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'), _total_num_issues AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH') SELECT NOW() AS time, 100 - CAST(100 * (SELECT 1.0 * num_issues_with_sprint_updated FROM _num_issues_with_sprint_updated) AS NUMERIC) / NULLIF((SELECT total_num_issues FROM _total_num_issues), 0) AS ratio", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "On-time Delivery [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 60 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 27 + }, + "id": 25, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time), _total_num_issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT x.time, 100 - 100 * (CAST(1.0 * x.num_issues_with_sprint_updated AS NUMERIC) / NULLIF(y.total_num_issues, 0)) AS delivery_rate FROM _num_issues_with_sprint_updated AS x JOIN _total_num_issues AS y ON x.time = y.time", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "On-time Delivery over Month", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "green", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 35 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE NOT pr.merged_date IS NULL AND CAST(pr.merged_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs merged [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 35 + }, + "id": 26, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _merged_prs AS (SELECT CAST(pr.merged_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS pr_merged_count FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, pr_merged_count FROM _merged_prs ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Merged over Month", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "orange", + "value": 20 + }, + { + "color": "red", + "value": 40 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 43 + }, + "id": 16, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Unlinked PRs % [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 43 + }, + "id": 28, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Unlinked PRs Ratio over Month", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 51 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND WEEKDAY(authored_date) BETWEEN 0 AND 4 AND CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' GROUP BY author_name, CAST(authored_date AS DATE)) SELECT CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) FROM _commits_groupby_name_and_date", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Coding Days % [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 51 + }, + "id": 29, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND (WEEKDAY(authored_date) BETWEEN 0 AND 4) AND $__timeFilter(authored_date) AND authored_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1, 2) SELECT _day + INTERVAL '1 day' AS time, CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) AS working_days_percentatages_per_month FROM _commits_groupby_name_and_date GROUP BY time", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Percentage of Coding Days Each Month", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 59 + }, + "id": 2, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_raw_github_api_repositories", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Time to Merge [in $month]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 59 + }, + "id": 30, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS pr_time_to_merge_in_days FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.created_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_raw_github_api_repositories", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Time to Merge over Month", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 21, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS 'Bug', COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS 'Strategic', COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS 'Non-Strategic' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Work Done % [in $month]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 68, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 20, + "options": { + "barRadius": 0, + "barWidth": 0.27, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.priority AS 'Priority', AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS 'Average Age' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY i.priority", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "_devlake_blueprints", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Average Age of Critical Outstanding Defects by Priority", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 34, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard", + "Highlights" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "select\n distinct(concat(date_format(DATE_ADD(date(created_date), INTERVAL -DAY(date(created_date))+1 DAY), '%Y-%m') , ':', date_format(DATE_ADD(date(created_date), INTERVAL -DAY(date(created_date))+1 DAY), '%Y-%m-%d'))) as month\nfrom\n issues i\norder by month desc", + "hide": 0, + "includeAll": false, + "label": "Selected Month", + "multi": false, + "name": "month", + "options": [], + "query": "select\n distinct(concat(date_format(month_timestamp, '%Y-%m') , ':', date_format(month_timestamp, '%Y-%m-%d'))) as month\nfrom\n calendar_months\nwhere \n month_timestamp <= curdate()\norder by month desc\nlimit 12", + "refresh": 1, + "regex": "/^(?.*):(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct priority from issues", + "description": "Customize what prioriti(es) are considered \"critical\"", + "hide": 0, + "includeAll": true, + "label": "Prioriti(es) of Critical Issues", + "multi": true, + "name": "priority", + "options": [], + "query": "select distinct priority from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "Engineering Overview (PostgreSQL)", + "uid": "ZF6abXX7z-pg", + "version": 15, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json new file mode 100644 index 00000000000..47b475a46fd --- /dev/null +++ b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json @@ -0,0 +1,1964 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 111, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the engineering throughput and and cycle time, which helps to identify productivity and bottlenecks of the development process.\n- Data Sources Required:\n - One of the Git tools, e.g. [GitHub](https://devlake.apache.org/docs/Configuration/GitHub), [GitLab](https://devlake.apache.org/docs/Configuration/GitLab), [Bitbucket](https://devlake.apache.org/docs/Configuration/BitBucket) or [Azure DevOps](https://devlake.apache.org/docs/Configuration/AzureDevOps)\n - One of the issue tracking tools, e.g. [Jira](https://devlake.apache.org/docs/Configuration/Jira) ([Scope Config](https://devlake.apache.org/docs/UserManuals/ConfigUI/Jira#step-3---adding-transformation-rules-optional) required to tell DevLake what the story_points field is)", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 89, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Throughput/Quality", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 4 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS \"PR: Opened\", COUNT(DISTINCT CASE WHEN NOT pr.merged_date IS NULL THEN id ELSE NULL END) AS \"PR: Merged\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Opened/Merged", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Issues Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Issues Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 4 + }, + "id": 74, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS 'Issues Opened', COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS 'Issues Completed' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Opened/Completed", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Story Points", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 4 + }, + "id": 98, + "links": [ + { + "targetBlank": true, + "title": "Requirement Granularity", + "url": "https://devlake.apache.org/docs/Metrics/RequirementGranularity" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS 'Story Points Completed' FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Story Points Completed", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "PR Review Depth", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 11 + }, + "id": 99, + "links": [ + { + "targetBlank": true, + "title": "PR Review Depth", + "url": "https://devlake.apache.org/docs/Metrics/PRReviewDepth" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT prc.id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS \"PR Review Depth\" FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Review Depth", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Bug Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 11 + }, + "id": 100, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS 'P0/P1 Bugs' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "P0/P1 Bugs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "PR Merged Size", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 11 + }, + "id": 101, + "links": [ + { + "targetBlank": true, + "title": "PR Size", + "url": "https://devlake.apache.org/docs/Metrics/PRSize" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS 'PR Merged Size' FROM _pr_commits_data GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Merged Size", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 108, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN NOT pr.id IN (SELECT pull_request_id FROM pull_request_comments) THEN 1 ELSE 0 END) AS \"PRs Merged w/o Review\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Merged w/o Review", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 103, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Cycle Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 109, + "links": [ + { + "targetBlank": true, + "title": "PR Cycle Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCycleTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(cycle_time) AS 'PR Cycle Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Cycle Time [Merged PRs]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 33 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "PR Coding Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCodingTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(coding_time) AS 'Coding Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Coding Time [Merged PRs]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 33 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "PR Pickup Time", + "url": "https://devlake.apache.org/docs/Metrics/PRPickupTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(pickup_time) AS 'Pickup Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Pickup Time", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 33 + }, + "id": 118, + "links": [ + { + "targetBlank": true, + "title": "PR Review Time", + "url": "https://devlake.apache.org/docs/Metrics/PRReviewTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(review_time) AS 'Review Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Review Time [Merged PRs]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 33 + }, + "id": 119, + "links": [ + { + "targetBlank": true, + "title": "PR Deploy Time", + "url": "https://devlake.apache.org/docs/Metrics/PRDeployTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(deploy_time) AS 'PR Deploy Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Deploy Time [Merged PRs]", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 113, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard", + "Highlights" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct priority from issues where priority != ''", + "hide": 0, + "includeAll": true, + "label": "P0+P1 Priority", + "multi": true, + "name": "priority", + "options": [], + "query": "select distinct priority from issues where priority != ''", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Month", + "value": "DAYOFMONTH" + }, + "description": "", + "hide": 0, + "includeAll": false, + "label": "Time Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Engineering Throughput and Cycle Time (PostgreSQL)", + "uid": "Jaaimc67k-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json new file mode 100644 index 00000000000..7588c90d806 --- /dev/null +++ b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json @@ -0,0 +1,3419 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 9, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 142, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the engineering throughput and and cycle time, which helps to identify productivity and bottlenecks of the development process.\n- Data Sources Required:\n - One of the Git tools, e.g. [GitHub](https://devlake.apache.org/docs/Configuration/GitHub), [GitLab](https://devlake.apache.org/docs/Configuration/GitLab), [Bitbucket](https://devlake.apache.org/docs/Configuration/BitBucket) or [Azure DevOps](https://devlake.apache.org/docs/Configuration/AzureDevOps)\n - One of the issue tracking tools, e.g. [Jira](https://devlake.apache.org/docs/Configuration/Jira) ([Scope Config](https://devlake.apache.org/docs/UserManuals/ConfigUI/Jira#step-3---adding-transformation-rules-optional) required to tell DevLake what the story_points field is)\n- You also need to do [team configuration](https://devlake.apache.org/docs/Configuration/TeamConfiguration) to use this dashboard.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 89, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Total and Avg PRs Opened/Merged", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Total PR Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Total PR Opened" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 111, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: Total PR Opened\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: Total PR Opened\" FROM _prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total PRs Opened", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: PR Opened per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: PR Opened per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 110, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Opened per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Opened per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Opened per Member\" FROM _prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Opened per Member", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Total PR Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Total PR Merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 11 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND merged_date IS NULL THEN NULL END) AS \"Team1: Total PR Merged\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND merged_date IS NULL THEN NULL END) AS \"Team2: Total PR Merged\" FROM _prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total PRs Merged", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: PR Merged per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: PR Merged per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 112, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN id END) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged per Member\" FROM _prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Merged per Member", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 114, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Completed", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Issues Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Issues Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 74, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS 'Team1: Issues Completed' /* count(i.id) as 'Issues Opened', */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS 'Team2: Issues Completed' FROM _issues GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Issue Completed", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Issues Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Issues Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 19 + }, + "id": 115, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: Issues Completed per Member', CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: Issues Completed per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Completed per Member", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 126, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "3. Story Points Completed", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Story Points Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Story Points Completed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS 'Team1: Story Points Completed' /* count(i.id) as 'Issues Opened', */, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS 'Team2: Story Points Completed' FROM _issues GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Story Points Completed", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Story Points Completed per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Story Points Completed per Member" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: Story Points Completed per Member', CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: Story Points Completed per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Story Points Completed per Member", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 124, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "4. PR Review Depth and PR Merged Size", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: PR Review Depth" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: PR Review Depth" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 122, + "links": [ + { + "targetBlank": true, + "title": "PR Review Depth", + "url": "https://devlake.apache.org/docs/Metrics/PRReviewDepth" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, prc.id AS comment_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Review Depth\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Review Depth\", CAST(COUNT(DISTINCT comment_id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Review Depth\" FROM _merged_prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": " Average PR Review Depth per Team", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Request Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: PR Size" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: PR Size" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 127, + "links": [ + { + "targetBlank": true, + "title": "PR Size", + "url": "https://devlake.apache.org/docs/Metrics/PRSize" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, pr.merge_commit_sha, c.additions + c.deletions AS loc, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Size\", CAST(SUM(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Size\", CAST(SUM(loc) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged Size\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": " Average PR Merged Size by Team", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 119, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "5. P0/P1 Bugs", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Bug Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: P0/P1 Bugs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: P0/P1 Bugs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 100, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS 'Team1: P0/P1 Bugs', COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS 'Team2: P0/P1 Bugs' FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total P0/P1 Bugs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Bug Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: P0/P1 Bugs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: P0/P1 Bugs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: P0/P1 Bugs per Member', CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: P0/P1 Bugs per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "P0/P1 Bugs per Member", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 50 + }, + "id": 103, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "6. Cycle Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Avg Cycle Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Avg Cycle Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 51 + }, + "id": 136, + "links": [ + { + "targetBlank": true, + "title": "PR Cycle Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCycleTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN cycle_time END) AS 'Team1: Avg Cycle Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN cycle_time END) AS 'Team2: Avg Cycle Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Cycle Time(h)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Avg Coding Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Avg Coding Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 58 + }, + "id": 140, + "links": [ + { + "targetBlank": true, + "title": "PR Coding Time", + "url": "https://devlake.apache.org/docs/Metrics/PRCodingTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN coding_time END) AS 'Team1: Avg Coding Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN coding_time END) AS 'Team2: Avg Coding Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Coding Time(h)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Avg Pickup Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Avg Pickup Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 58 + }, + "id": 134, + "links": [ + { + "targetBlank": true, + "title": "PR Pickup Time", + "url": "https://devlake.apache.org/docs/Metrics/PRPickupTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN pickup_time END) AS 'Team1: Avg Pickup Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN pickup_time END) AS 'Team2: Avg Pickup Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Pickup Time(h)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Avg Review Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Avg Review Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 58 + }, + "id": 135, + "links": [ + { + "targetBlank": true, + "title": "PR Review Time", + "url": "https://devlake.apache.org/docs/Metrics/PRReviewTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN review_time END) AS 'Team1: Avg Review Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN review_time END) AS 'Team2: Avg Review Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Review Time(h)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: Avg Review Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: Avg Review Time(h)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 58 + }, + "id": 145, + "links": [ + { + "targetBlank": true, + "title": "PR Deploy Time", + "url": "https://devlake.apache.org/docs/Metrics/PRDeployTime" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN deploy_time END) AS 'Team1: Avg Deploy Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN deploy_time END) AS 'Team2: Avg Deploy Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PR Deploy Time(h)", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 65 + }, + "id": 139, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "7. PRs Merged w/o Review", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Team1: PRs Merged w/o Review" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Team2: PRs Merged w/o Review" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 66 + }, + "id": 108, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team1: PRs Merged w/o Review\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team2: PRs Merged w/o Review\" FROM _merged_prs GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "PRs Merged w/o Review", + "type": "timeseries" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 73 + }, + "id": 144, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": "", + "current": { + "isNone": true, + "selected": false, + "text": "None", + "value": "" + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from teams", + "hide": 0, + "includeAll": false, + "label": "Team1", + "multi": false, + "name": "team1", + "options": [], + "query": "select concat(name, '--', id) as text from teams", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": "", + "current": { + "isNone": true, + "selected": false, + "text": "None", + "value": "" + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from teams", + "hide": 0, + "includeAll": false, + "label": "Team2", + "multi": false, + "name": "team2", + "options": [], + "query": "select concat(name, '--', id) as text from teams", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct priority from issues where priority != ''", + "hide": 0, + "includeAll": true, + "label": "P0+P1 Priority", + "multi": true, + "name": "priority", + "options": [], + "query": "select distinct priority from issues where priority != ''", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Month", + "value": "DAYOFMONTH" + }, + "description": "", + "hide": 0, + "includeAll": false, + "label": "Time Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Engineering Throughput and Cycle Time - Team View (PostgreSQL)", + "uid": "nJ1ijje7k-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/GitHub.json b/grafana/dashboards/postgresql/GitHub.json new file mode 100644 index 00000000000..8db63259901 --- /dev/null +++ b/grafana/dashboards/postgresql/GitHub.json @@ -0,0 +1,3658 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 35, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 99, + "links": [ + { + "targetBlank": true, + "title": "GitHub", + "url": "https://devlake.apache.org/docs/Plugins/github" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic Git and Code Review metrics from GitHub.\n- Data Source Required: GitHub", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 89, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. User Requirements (Issues)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 7, + "x": 0, + "y": 4 + }, + "id": 62, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Number of New Issues", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Issue Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 17, + "x": 7, + "y": 4 + }, + "id": 74, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_count AS \"Issue Count\" FROM _issues ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.2 Number of New Issues", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 87, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. How issues are handled?", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 7, + "x": 0, + "y": 11 + }, + "id": 43, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\"", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Number of Closed Issues", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "closed_issue_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 17, + "x": 7, + "y": 11 + }, + "id": 111, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS open_issue_count, COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS closed_issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open_issue_count, closed_issue_count FROM _issues ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.2 Number of open and closed issues by Issue creation time", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 7, + "x": 0, + "y": 17 + }, + "id": 64, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\"", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.3 Mean Issue Lead Time by issue resolution time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 17, + "x": 7, + "y": 17 + }, + "id": 75, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\" AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.4 Mean issue lead time by issue resolution time", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Queue Time", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 93, + "links": [], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the queue time of all outstanding bugs */ WITH _outstanding_issues AS (SELECT DISTINCT b.name AS repo_name, i.issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(i.created_date) AND i.status <> 'DONE') SELECT CONCAT('#', issue_key) AS issue_key, title, url, queue_time_in_days FROM _outstanding_issues ORDER BY 4 DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.5 Top 20 Queue Time [All Open Issues]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 110 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 718 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "created_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 149 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queue_time_in_days" + }, + "properties": [ + { + "id": "custom.width", + "value": 155 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "repo_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 256 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.url}" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 92, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the queue time of all outstanding bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(i.created_date) AND i.status <> 'DONE' ORDER BY queue_time_in_days DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.6 List of Outstanding Issues Order By Queue Time [All Open Issues]", + "type": "table" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 83, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "3. Contribution (PRs)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 36 + }, + "id": 68, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of New Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Pull Request Count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 36 + }, + "id": 77, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.2 Number of New Pull Requests", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Merged PR Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 59, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.3 Top 20 Contributors By Merged PRs", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 48 + }, + "id": 85, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "4. How PRs are handled?", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.05 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 49 + }, + "id": 66, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.1 Ratio of unmerged PRs of All Closed PRs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "PR: Open" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed without merging" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR: Closed and merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 49 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.2 Pull Request Status Distribution", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 55 + }, + "id": 80, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.3 Number of PRs Closed without Merging", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Ratio", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 1, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 15, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 55 + }, + "id": 81, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.4 Ratio of unmerged PRs of All Closed PRs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 61 + }, + "id": 72, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.5 Mean Time to Merge of Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 61 + }, + "id": 95, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.6 Mean Time to Merge of Pull Requests in Days", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 67 + }, + "id": 96, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND status IN ('CLOSED', 'MERGED')", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.7 Mean Time to Close of Pull Requests in Days", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 67 + }, + "id": 97, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND status IN ('CLOSED', 'MERGED') GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_close AS \"Time to Close\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.8 Mean Time to Close of Pull Requests in Days", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 73 + }, + "id": 102, + "panels": [], + "title": "5. CI/CD Metrics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Number of workflow runs executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 74 + }, + "id": 103, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.1 Total Number of Successful workflow runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful workflow runs / Number of total workflow runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 74 + }, + "id": 104, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.2 Mean Workflow Runs Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_workflow_run_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_workflow_run_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 74 + }, + "id": 109, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.25, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_workflow_run_count, failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.3 Number of successful and failed workflow runs", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of successful workflow runs / Number of total workflow runs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 80 + }, + "id": 105, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.4 Total Workflow Runs Result Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Workflow Runs success rate over time.\n2. The workflow runs being calculated are filtered by \"workflow runs starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Workflow Runs Success Rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 80 + }, + "id": 108, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.5 Workflow Runs Success Rate", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of successful workflow runs / Number of total workflow runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 86 + }, + "id": 106, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.6 Mean Workflow Runs Duration", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_sec" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 86 + }, + "id": 110, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.7 Mean Workflow Runs Duration", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 92 + }, + "id": 101, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos where id like 'github%'", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos where id like 'github%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Month", + "value": "DAYOFMONTH" + }, + "hide": 0, + "includeAll": false, + "label": "Time Interval", + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "GitHub (PostgreSQL)", + "uid": "KXWvOFQnz-pg", + "version": 25, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/GithubCopilotAdoption.json b/grafana/dashboards/postgresql/GithubCopilotAdoption.json new file mode 100644 index 00000000000..e9c76efeb20 --- /dev/null +++ b/grafana/dashboards/postgresql/GithubCopilotAdoption.json @@ -0,0 +1,2322 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": "postgresql", + "description": "Latest daily active users count", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT daily_active_users AS \"Daily Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1", + "refId": "A" + } + ], + "title": "Daily Active Users", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Code acceptance rate across the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "max": 100, + "unit": "percent", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 20 + }, + { + "color": "green", + "value": 40 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 2, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Code Acceptance Rate", + "type": "gauge" + }, + { + "datasource": "postgresql", + "description": "Total lines of code added by Copilot in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(loc_added_sum) AS \"Lines Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Lines of Code Added", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total pull requests created by Copilot in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(pr_total_created_by_copilot) AS \"PRs by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "PRs Created by Copilot", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Latest weekly active users count", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Weekly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '7 DAY' AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Weekly Active Users (WAU)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Latest monthly active users count", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 4 + }, + "id": 16, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Monthly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '28 DAY' AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Monthly Active Users (MAU)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total pull requests reviewed by Copilot in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 4 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(pr_total_reviewed_by_copilot) AS \"PRs Reviewed by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "PRs Reviewed by Copilot", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total user-initiated interactions in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 4 + }, + "id": 18, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(user_initiated_interaction_count) AS \"User-Initiated Interactions\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "User-Initiated Interactions", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Daily and monthly active users over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Users", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, daily_active_users AS \"Daily Active\", monthly_active_users AS \"Monthly Active\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Active Users Over Time", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Code suggestions generated vs accepted over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Count", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, code_generation_activity_count AS \"Suggestions\", code_acceptance_activity_count AS \"Acceptances\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Code Suggestions & Acceptances", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Lines of code suggested to add versus lines added over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lines of Code", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 19, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, loc_suggested_to_add_sum AS \"LOC Suggested\", loc_added_sum AS \"LOC Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "LOC Suggested vs LOC Added Over Time", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Pull request activity over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Pull Requests", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, pr_total_created AS \"PRs Created\", pr_total_created_by_copilot AS \"PRs Created by Copilot\", pr_total_reviewed AS \"PRs Reviewed\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "PR Activity Over Time", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly user-initiated interactions by top Copilot features", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Interactions", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "mode": "normal", + "group": "A" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "stacking": { + "mode": "normal", + "group": "A" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CAST(day - INTERVAL 'WEEKDAY DAY' AS DATE) AS time, CASE WHEN feature IN (SELECT feature FROM (SELECT feature FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY feature HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_features) THEN REPLACE(REPLACE(feature, 'chat_panel_', ''), '_mode', '') ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY time, metric ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Feature Mix Over Time", + "type": "timeseries", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "many" + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "^value (.+)$", + "renamePattern": "$1" + } + } + ] + }, + { + "datasource": "postgresql", + "description": "Weekly user-initiated interactions by top IDEs (Top 5 + Other)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Weekly Interactions", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "mode": "normal", + "group": "A" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "stacking": { + "mode": "normal", + "group": "A" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CAST(day - INTERVAL 'WEEKDAY DAY' AS DATE) AS time, CASE WHEN ide IN (SELECT ide FROM (SELECT ide FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY ide HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_ides) THEN ide ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY time, metric ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "IDE Adoption Over Time", + "type": "timeseries", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "many" + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "^value (.+)$", + "renamePattern": "$1" + } + } + ] + }, + { + "datasource": "postgresql", + "description": "Top languages ranked by code acceptance rate", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 40 + }, + "id": 9, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 15", + "refId": "A" + } + ], + "title": "Top Languages by Acceptance Rate", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Top models ranked by code acceptance rate", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 10, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY model HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 10", + "refId": "A" + } + ], + "title": "Top Models by Acceptance Rate", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Matrix-style breakdown of suggestions and acceptances by language and feature", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 48 + }, + "id": 21, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT language AS \"Language\", feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language, feature HAVING SUM(code_generation_activity_count) > 0 ORDER BY language NULLS FIRST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 100", + "refId": "A" + } + ], + "title": "Feature x Language Matrix", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Count of distinct users active in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 56 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Unique Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Unique Users (Period)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Users who have used Copilot agent mode", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 56 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Agent Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' AND used_agent = 1", + "refId": "A" + } + ], + "title": "Agent Mode Adopters", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Users who have used Copilot chat", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 56 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' AND used_chat = 1", + "refId": "A" + } + ], + "title": "Chat Adopters", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage of seats with recorded activity", + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "max": 100, + "unit": "percent", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "green", + "value": 75 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 56 + }, + "id": 14, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CAST(COUNT(CASE WHEN NOT last_activity_at IS NULL THEN 1 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(*), 0), 0) AS \"Utilization %\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id}", + "refId": "A" + } + ], + "title": "Seat Utilization", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 60 + }, + "id": 22, + "panels": [], + "title": "Quality & Efficiency Ratios", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Daily acceptance rate trend computed as acceptances divided by suggestions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "max": 1, + "min": 0, + "unit": "percentunit", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 61 + }, + "id": 23, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, CAST(code_acceptance_activity_count AS NUMERIC) / NULLIF(NULLIF(code_generation_activity_count, 0), 0) AS \"Acceptance Rate\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Overall Acceptance Rate Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily LOC yield trend computed as added LOC divided by suggested LOC", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 61 + }, + "id": 24, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, ROUND(CAST(loc_added_sum AS NUMERIC) / NULLIF(NULLIF(loc_suggested_to_add_sum, 0), 0), 2) AS \"LOC Yield\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "LOC Yield Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Acceptance ratio by Copilot feature", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 69 + }, + "id": 25, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY feature HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "Acceptance Rate by Feature", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Acceptance ratio by programming language", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 69 + }, + "id": 26, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "Acceptance Rate by Language", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Acceptance ratio by model", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 69 + }, + "id": 27, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY model HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "Acceptance Rate by Model", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 28, + "panels": [], + "title": "User Behavior", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Top users ranked by code generation activity in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 78 + }, + "id": 29, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT user_login AS \"user_login\", SUM(code_generation_activity_count) AS \"suggestions\", SUM(code_acceptance_activity_count) AS \"acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"acceptance %\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY user_login HAVING SUM(code_generation_activity_count) > 0 ORDER BY SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 25", + "refId": "A" + } + ], + "title": "Top Users by Code Generations", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Daily trend of distinct users engaging with agent mode versus chat", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Users", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 78 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT day AS time, COUNT(DISTINCT CASE WHEN used_agent = 1 THEN user_login END) AS \"Agent Users\", COUNT(DISTINCT CASE WHEN used_chat = 1 THEN user_login END) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Agent Users vs Chat Users Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "First-seen daily trend of users becoming active for the first time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Users", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 86 + }, + "id": 31, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT first_day AS time, COUNT(*) AS \"New Active Users\" FROM (SELECT user_login, MIN(day) AS first_day FROM _tool_copilot_user_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY user_login) AS first_seen WHERE $__timeFilter(first_day) GROUP BY first_day ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "New Active Users per Day", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 94 + }, + "id": 32, + "panels": [], + "title": "Seat Effectiveness", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Distribution of seat activity by last recorded editor", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 95 + }, + "id": 33, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(last_activity_editor, ''), 'unknown') AS \"Editor\", COUNT(*) AS \"Seats\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') GROUP BY COALESCE(NULLIF(last_activity_editor, ''), 'unknown') ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Seat Activity by Editor", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Total, active (last 30 days), and inactive seats for the selected scope", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 95 + }, + "id": 34, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(*) AS \"total_seats\", SUM(CASE WHEN NOT last_activity_at IS NULL AND last_activity_at >= UTC_TIMESTAMP() - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"active_seats\", SUM(CASE WHEN last_activity_at IS NULL OR last_activity_at < UTC_TIMESTAMP() - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"inactive_seats\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')", + "refId": "A" + } + ], + "title": "Seats Summary", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Seats with no activity or activity older than 30 days", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 95 + }, + "id": 35, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT user_login AS \"user_login\", last_activity_at AS \"last_activity_at\", plan_type AS \"plan_type\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') AND (last_activity_at IS NULL OR last_activity_at < UTC_TIMESTAMP() - INTERVAL '30 DAY') ORDER BY last_activity_at IS NULL DESC NULLS LAST, last_activity_at ASC NULLS FIRST LIMIT 100", + "refId": "A" + } + ], + "title": "Inactive Seats", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 103 + }, + "id": 36, + "panels": [], + "title": "Diagnostics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Latest available day in enterprise-level Copilot metrics", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 104 + }, + "id": 37, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE((EXTRACT(EPOCH FROM (UTC_DATE() - MAX(day)))/86400), 9999) AS \"Days Since Latest Data\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Data Freshness", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Share of generated suggestions tied to unknown model/feature taxonomy", + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "max": 100, + "unit": "percent", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 5 + }, + { + "color": "red", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 104 + }, + "id": 38, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN LOWER(COALESCE(model, '')) = 'unknown' OR LOWER(COALESCE(feature, '')) LIKE '%unknown%' THEN code_generation_activity_count ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 2) AS \"Unknown Taxonomy %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "refId": "A" + } + ], + "title": "Unknown Taxonomy Share", + "type": "gauge" + }, + { + "datasource": "postgresql", + "description": "Row counts for core Copilot metrics tables", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 104 + }, + "id": 39, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT * FROM (SELECT '_tool_copilot_enterprise_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_user_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_user_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_feature WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_model_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_model_feature WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_seats' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')) AS data_volume ORDER BY row_count DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Data Volume by Table", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 38, + "tags": [ + "copilot", + "devlake" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_enterprise_daily_metrics ORDER BY 1", + "hide": 0, + "includeAll": false, + "label": "Connection ID", + "multi": false, + "name": "connection_id", + "options": [], + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT id as scope_id FROM _tool_copilot_scopes WHERE connection_id = ${connection_id} ORDER BY 1", + "hide": 0, + "includeAll": false, + "label": "Scope ID", + "multi": false, + "name": "scope_id", + "options": [], + "query": "SELECT DISTINCT id as scope_id FROM _tool_copilot_scopes WHERE connection_id = ${connection_id} ORDER BY 1", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "GitHub Copilot Adoption (PostgreSQL)", + "uid": "copilot_adoption-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json b/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json new file mode 100644 index 00000000000..19cfb137c71 --- /dev/null +++ b/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json @@ -0,0 +1,2931 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": "postgresql", + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "Copilot Implementation Date", + "rawQuery": "SELECT\n UNIX_TIMESTAMP(implementation_date) * 1000 AS time,\n 'Copilot Rollout' AS text,\n 'Implementation Date' AS title\nFROM _tool_copilot_scopes\nWHERE id = '${scope_id}'\n AND implementation_date IS NOT NULL", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "## GitHub Copilot + DORA Correlation Dashboard\n\n**Purpose**: Analyze the **correlation** between GitHub Copilot adoption levels and engineering productivity metrics.\n\n**How it works**:\n- \ud83d\udcc8 **Primary Analysis**: Continuous correlation showing how DORA metrics trend alongside GitHub Copilot adoption intensity\n- \ud83d\udcca **Adoption Tiers**: Metrics grouped by adoption level (<25%, 25-50%, 50-75%, >75% of seats active)\n- \ud83d\udccd **Optional Milestone**: Configure a rollout date to add annotation markers on charts\n\n**Prerequisites**:\n- Ensure **GitHub Copilot metrics** are being collected via the GitHub Copilot plugin\n- Ensure **PR data** exists in DevLake from GitHub/GitLab plugins\n- For DORA metrics (CFR, MTTR): Configure deployments and incidents\n- For Code Quality: Configure SonarQube integration (optional)\n\n**\u26a0\ufe0f Disclaimer**: Correlation does not imply causation. Other factors may influence the metrics shown.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "Weekly GitHub Copilot adoption percentage (active users / total seats) over time. Higher adoption correlates with expected productivity improvements.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Adoption %", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, active_users, total_seats, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, ROUND(AVG(adoption_pct), 1) AS adoption_pct, ROUND(AVG(active_users), 0) AS avg_active_users, ROUND(AVG(total_seats), 0) AS avg_total_seats FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, adoption_pct AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "refId": "A" + } + ], + "title": "GitHub Copilot Adoption Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient (r) between GitHub Copilot adoption % and PR cycle time. Negative values indicate inverse correlation (higher adoption \u2192 faster PRs). |r| > 0.7 = strong, 0.3-0.7 = moderate, < 0.3 = weak.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "from": -1, + "result": { + "color": "green", + "index": 0, + "text": "Strong \u2193" + }, + "to": -0.7 + }, + "type": "range" + }, + { + "options": { + "from": -0.7, + "result": { + "color": "yellow", + "index": 1, + "text": "Moderate \u2193" + }, + "to": -0.3 + }, + "type": "range" + }, + { + "options": { + "from": -0.3, + "result": { + "color": "text", + "index": 2, + "text": "Weak" + }, + "to": 0.3 + }, + "type": "range" + }, + { + "options": { + "from": 0.3, + "result": { + "color": "orange", + "index": 3, + "text": "Moderate \u2191" + }, + "to": 0.7 + }, + "type": "range" + }, + { + "options": { + "from": 0.7, + "result": { + "color": "red", + "index": 4, + "text": "Strong \u2191" + }, + "to": 1 + }, + "type": "range" + } + ], + "noValue": "\u26a0\ufe0f Insufficient data (need 4+ weeks)", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none", + "decimals": 2 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 20, + "y": 31 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _correlation_data AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start WHERE NOT aw.adoption_pct IS NULL AND NOT pm.avg_cycle_time_hours IS NULL), _stats AS (SELECT COUNT(*) AS n, AVG(adoption_pct) AS mean_x, AVG(avg_cycle_time_hours) AS mean_y, STDDEV_POP(adoption_pct) AS stddev_x, STDDEV_POP(avg_cycle_time_hours) AS stddev_y FROM _correlation_data), _pearson AS (SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.stddev_x = 0 OR s.stddev_y = 0 THEN 0 ELSE (SELECT CAST(SUM((cd.adoption_pct - s.mean_x) * (cd.avg_cycle_time_hours - s.mean_y)) AS NUMERIC) / NULLIF((s.n * s.stddev_x * s.stddev_y), 0) FROM _correlation_data AS cd) END AS r FROM _stats AS s) SELECT ROUND(r, 2) AS value FROM _pearson", + "refId": "A" + } + ], + "title": "Adoption vs PR Cycle Time (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Current week's GitHub Copilot adoption percentage (active users / total seats)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "No data", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 25 + }, + { + "color": "green", + "value": 50 + }, + { + "color": "dark-green", + "value": 75 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 14 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT daily_active_users AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1) AS _ent UNION ALL SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY date DESC NULLS LAST LIMIT 1) AS _org LIMIT 1", + "refId": "A" + } + ], + "title": "Current Adoption %", + "type": "stat" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 10 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "### Interpreting Correlation (r)\n\n| Value | Meaning |\n|-------|--------|\n| **-1 to -0.7** | \ud83d\udfe2 Strong inverse (higher adoption \u2192 faster PRs) |\n| **-0.7 to -0.3** | \ud83d\udfe1 Moderate inverse |\n| **-0.3 to 0.3** | \u26aa Weak/No correlation |\n| **0.3 to 0.7** | \ud83d\udfe0 Moderate positive |\n| **0.7 to 1** | \ud83d\udd34 Strong positive (unexpected) |\n\n*Note: r < 0 is expected (adoption \u2191 = cycle time \u2193)*", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "\ud83d\udcca How to Read Correlation", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 100, + "panels": [], + "title": "PR Velocity Impact", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Percentage difference in PR cycle time between high adoption weeks (>50%) and low adoption weeks (<50%). Negative values indicate improvement (faster PRs during high adoption).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "\u26a0\ufe0f Insufficient data", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0 + }, + { + "color": "red", + "value": 10 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 16, + "y": 31 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct >= 50 THEN 'high' ELSE 'low' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start), _tier_averages AS (SELECT adoption_tier, AVG(avg_cycle_time_hours) AS avg_cycle_time FROM _adoption_pr_joined GROUP BY adoption_tier) SELECT ROUND(CAST(((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'high') - (SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low'), 0), 0) * 100, 1) AS value", + "refId": "A" + } + ], + "title": "PR Cycle Time Change (High vs Low Adoption)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Dual-axis chart showing weekly GitHub Copilot adoption % (left axis) and PR cycle time in hours (right axis). Look for inverse correlation - adoption up, cycle time down.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Adoption %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "max", + "value": 100 + }, + { + "id": "min", + "value": 0 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR Cycle Time (hrs)" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "h" + }, + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "refId": "Adoption" + }, + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(avg_cycle_time_hours, 1) AS 'PR Cycle Time (hrs)' FROM _pr_metrics_weekly ORDER BY week_start NULLS FIRST", + "refId": "PRTime" + } + ], + "title": "Adoption vs PR Cycle Time", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Average PR cycle time by adoption tier. Lower times in higher adoption tiers suggest positive Copilot impact.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 24 + }, + { + "color": "red", + "value": 48 + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 12, + "y": 31 + }, + "id": 14, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "transformations": [ + { + "id": "rowsToFields", + "options": { + "labelField": "metric", + "valueField": "value" + } + } + ], + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct < 25 THEN '1. <25%' WHEN aw.adoption_pct < 50 THEN '2. 25-50%' WHEN aw.adoption_pct < 75 THEN '3. 50-75%' ELSE '4. >75%' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start) SELECT adoption_tier AS metric, ROUND(AVG(avg_cycle_time_hours), 1) AS value FROM _adoption_pr_joined GROUP BY adoption_tier ORDER BY adoption_tier NULLS FIRST", + "refId": "A" + } + ], + "title": "PR Cycle Time by Adoption Tier", + "type": "bargauge" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 12, + "y": 35 + }, + "id": 15, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "**Cycle Time Components** break down PR lifecycle into three phases:\n\n- **Coding Time**: PR creation \u2192 first commit (development speed)\n- **Pickup Time**: PR creation \u2192 first review (reviewer responsiveness)\n- **Review Time**: First review \u2192 merge (actual review duration)\n\nComparing <50% vs \u226550% adoption periods shows which phase improved most with Copilot. For example, if Coding Time shrinks significantly, Copilot accelerates development.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Understanding Cycle Time Breakdown", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "Scatter plot showing weekly data points. Each point = one week. X-axis = adoption %, Y-axis = PR cycle time. Downward trend line indicates positive Copilot impact.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Adoption %", + "axisPlacement": "auto", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "pointSize": { + "fixed": 10 + }, + "scaleDistribution": { + "type": "linear" + }, + "show": "points" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 11, + "options": { + "dims": { + "x": "adoption_pct" + }, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "series": [], + "seriesMapping": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT ROUND(aw.adoption_pct, 1) AS adoption_pct, ROUND(pm.avg_cycle_time_hours, 1) AS cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start ORDER BY aw.adoption_pct NULLS FIRST", + "refId": "A" + } + ], + "title": "Adoption vs PR Cycle Time (Scatter)", + "type": "xychart" + }, + { + "datasource": "postgresql", + "description": "PR cycle time breakdown during low adoption periods (<50%). Compare with high adoption to see which components improved.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 16, + "y": 35 + }, + "id": 12, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _low_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct < 50) SELECT 'Coding Time' AS component, ROUND(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks)", + "refId": "A" + } + ], + "title": "Cycle Time Components: Low Adoption", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "PR cycle time breakdown during high adoption periods (>=50%). Compare with low adoption to see which components improved.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 20, + "y": 35 + }, + "id": 13, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _high_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct >= 50) SELECT 'Coding Time' AS component, ROUND(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks)", + "refId": "A" + } + ], + "title": "Cycle Time Components: High Adoption", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 200, + "panels": [], + "title": "Deployment Frequency Impact", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Dual-axis chart showing GitHub Copilot adoption percentage and deployment frequency over time. Look for correlation patterns - does higher adoption correlate with more frequent deployments?", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Adoption %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "min", + "value": 0 + }, + { + "id": "max", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Deployments" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 46 + }, + "id": 31, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', COALESCE(dw.deploy_count, 0) AS 'Deployments' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "refId": "A" + } + ], + "title": "Adoption vs Deployment Frequency", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Deployment frequency grouped by GitHub Copilot adoption tier. Higher adoption should correlate with more frequent deployments if Copilot accelerates development velocity.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "yellow", + "value": null + }, + { + "color": "green", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 46 + }, + "id": 32, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "transformations": [ + { + "id": "rowsToFields", + "options": { + "labelField": "Adoption Tier", + "valueField": "Deploys/Week" + } + } + ], + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(COALESCE(dw.deploy_count, 0)), 1) AS 'Deploys/Week' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "refId": "A" + } + ], + "title": "Deployments per Week by Adoption Tier", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient (r) between GitHub Copilot adoption % and weekly deployment count. Positive values indicate higher adoption correlates with more deployments.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.3 + }, + { + "color": "green", + "value": 0.7 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 46 + }, + "id": 33, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * deploy_count) - SUM(adoption_pct) * SUM(deploy_count)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(deploy_count * deploy_count) - SUM(deploy_count) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined WHERE NOT adoption_pct IS NULL", + "refId": "A" + } + ], + "title": "Adoption vs Deployment Frequency (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage change in deployment frequency between low adoption (<50%) and high adoption (\u226550%) periods.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0 + }, + { + "color": "green", + "value": 20 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 50 + }, + "id": 34, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _adoption_deploy AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start), _tier_avg AS (SELECT tier, AVG(deploy_count) AS avg_deploys FROM _adoption_deploy GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_deploys FROM _tier_avg WHERE tier = 'high') - (SELECT avg_deploys FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_deploys FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "refId": "A" + } + ], + "title": "Deployment Frequency Change (High vs Low Adoption)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 250, + "panels": [], + "title": "Adoption Intensity Analysis", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Comprehensive comparison of all DORA metrics across GitHub Copilot adoption tiers. Lower values (green) are better for cycle time, CFR, and MTTR. Higher values (green) are better for deployment frequency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 72, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, adoption_pct, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, AVG(CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0)) AS cycle_time_hrs FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND NOT pr_merged_date IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _cfr_weekly AS (SELECT week_start, ROUND(CAST(failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(deploy_count, 0), 0), 1) AS cfr_pct FROM _deploy_weekly), _mttr_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _tier_metrics AS (SELECT at.tier, ROUND(AVG(pw.cycle_time_hrs), 1) AS pr_cycle_time, ROUND(AVG(dw.deploy_count), 1) AS deploy_freq, ROUND(AVG(cfr.cfr_pct), 1) AS cfr_pct, ROUND(AVG(mttr.mttr_hours), 1) AS mttr_hours FROM _adoption_tiers AS at LEFT JOIN _pr_weekly AS pw ON at.week_start = pw.week_start LEFT JOIN _deploy_weekly AS dw ON at.week_start = dw.week_start LEFT JOIN _cfr_weekly AS cfr ON at.week_start = cfr.week_start LEFT JOIN _mttr_weekly AS mttr ON at.week_start = mttr.week_start GROUP BY at.tier) SELECT tier AS 'Adoption Tier', COALESCE(pr_cycle_time, 0) AS 'PR Cycle Time (hrs)', COALESCE(deploy_freq, 0) AS 'Deploys/Week', COALESCE(cfr_pct, 0) AS 'CFR %', COALESCE(mttr_hours, 0) AS 'MTTR (hours)' FROM _tier_metrics ORDER BY FIELD(tier, '<25%', '25-50%', '50-75%', '>75%') NULLS FIRST", + "refId": "A" + } + ], + "title": "DORA Metrics by Adoption Tier", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 300, + "panels": [], + "title": "Change Failure Rate Impact", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Dual-axis chart showing GitHub Copilot adoption percentage and Change Failure Rate over time. CFR should decrease (improve) as adoption increases if Copilot improves code quality.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Adoption %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "min", + "value": 0 + }, + { + "id": "max", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "CFR %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 55 + }, + "id": 41, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', ROUND(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0), 1) AS 'CFR %' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "refId": "A" + } + ], + "title": "Adoption vs Change Failure Rate", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Change Failure Rate grouped by GitHub Copilot adoption tier. Lower CFR at higher adoption indicates Copilot improves code quality and reduces failures.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 15 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 55 + }, + "id": 42, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "transformations": [ + { + "id": "rowsToFields", + "options": { + "labelField": "Adoption Tier", + "valueField": "CFR %" + } + } + ], + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)), 1) AS 'CFR %' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "refId": "A" + } + ], + "title": "CFR by Adoption Tier", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient (r) between GitHub Copilot adoption % and Change Failure Rate. NEGATIVE values are good - they indicate higher adoption correlates with lower failure rates.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -0.3 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 55 + }, + "id": 43, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * cfr_pct) - SUM(adoption_pct) * SUM(cfr_pct)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(cfr_pct * cfr_pct) - SUM(cfr_pct) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "refId": "A" + } + ], + "title": "Adoption vs CFR (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage change in Change Failure Rate between low adoption (<50%) and high adoption (\u226550%) periods. Negative values indicate improvement.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -10 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 59 + }, + "id": 44, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _adoption_cfr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0), _tier_avg AS (SELECT tier, AVG(cfr_pct) AS avg_cfr FROM _adoption_cfr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_cfr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_cfr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cfr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "refId": "A" + } + ], + "title": "CFR Change (High vs Low Adoption)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 63 + }, + "id": 400, + "panels": [], + "title": "Recovery Time (MTTR) Impact", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Dual-axis chart showing GitHub Copilot adoption percentage and Mean Time to Recovery (MTTR) over time. MTTR should decrease (improve) as adoption increases if Copilot helps faster incident resolution.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Adoption %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "min", + "value": 0 + }, + { + "id": "max", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "MTTR (hours)" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "h" + }, + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 64 + }, + "id": 51, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "refId": "Adoption" + }, + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(mttr_hours, 1) AS 'MTTR (hours)' FROM _incidents_weekly ORDER BY week_start NULLS FIRST", + "refId": "MTTR" + } + ], + "title": "Adoption vs MTTR", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Mean Time to Recovery grouped by GitHub Copilot adoption tier. Lower MTTR at higher adoption indicates Copilot helps faster incident resolution.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 24 + }, + { + "color": "red", + "value": 72 + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 64 + }, + "id": 52, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "transformations": [ + { + "id": "rowsToFields", + "options": { + "labelField": "Adoption Tier", + "valueField": "MTTR (hours)" + } + } + ], + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(iw.mttr_hours), 1) AS 'MTTR (hours)' FROM _adoption_weekly AS aw LEFT JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start WHERE NOT iw.mttr_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "refId": "A" + } + ], + "title": "MTTR by Adoption Tier", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient (r) between GitHub Copilot adoption % and MTTR. NEGATIVE values are good - they indicate higher adoption correlates with faster recovery.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -0.3 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 64 + }, + "id": 53, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * mttr_hours) - SUM(adoption_pct) * SUM(mttr_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(mttr_hours * mttr_hours) - SUM(mttr_hours) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "refId": "A" + } + ], + "title": "Adoption vs MTTR (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage change in MTTR between low adoption (<50%) and high adoption (\u226550%) periods. Negative values indicate faster recovery.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -20 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 68 + }, + "id": 54, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _adoption_mttr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start), _tier_avg AS (SELECT tier, AVG(mttr_hours) AS avg_mttr FROM _adoption_mttr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_mttr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_mttr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_mttr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "refId": "A" + } + ], + "title": "MTTR Change (High vs Low Adoption)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 500, + "panels": [], + "title": "Code Review Time Impact", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Code review time (time from first review request to merge) grouped by GitHub Copilot adoption tier. Shorter review times at higher adoption may indicate Copilot-assisted code is easier to review.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 24 + }, + { + "color": "red", + "value": 48 + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 73 + }, + "id": 61, + "options": { + "displayMode": "gradient", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "transformations": [ + { + "id": "rowsToFields", + "options": { + "labelField": "Adoption Tier", + "valueField": "Review Time (hours)" + } + } + ], + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(prw.avg_review_time_hours), 1) AS 'Review Time (hours)' FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start WHERE NOT prw.avg_review_time_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "refId": "A" + } + ], + "title": "Review Time by Adoption Tier", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Dual-axis chart showing GitHub Copilot adoption % and average code review time over time. Look for patterns indicating whether Copilot-assisted PRs get reviewed faster.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Adoption %" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "min", + "value": 0 + }, + { + "id": "max", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Review Time (h)" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "h" + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 73 + }, + "id": 62, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', ROUND(prw.avg_review_time_hours, 1) AS 'Review Time (h)' FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start ORDER BY aw.week_start NULLS FIRST", + "refId": "A" + } + ], + "title": "Adoption vs Review Time Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Percentage change in code review time between low adoption (<50%) and high adoption (\u226550%) periods. Negative values indicate faster reviews.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -10 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 77 + }, + "id": 63, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_review AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start), _tier_avg AS (SELECT tier, AVG(avg_review_time_hours) AS avg_review FROM _adoption_review GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_review FROM _tier_avg WHERE tier = 'high') - (SELECT avg_review FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_review FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "refId": "A" + } + ], + "title": "Review Time Change (High vs Low Adoption)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient (r) between GitHub Copilot adoption % and code review time. NEGATIVE values indicate higher adoption correlates with faster reviews.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -0.3 + }, + { + "color": "red", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 73 + }, + "id": 64, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * avg_review_time_hours) - SUM(adoption_pct) * SUM(avg_review_time_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(avg_review_time_hours * avg_review_time_hours) - SUM(avg_review_time_hours) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "refId": "A" + } + ], + "title": "Adoption vs Review Time (r)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 81 + }, + "id": 600, + "panels": [], + "title": "Code Quality Impact (Optional - Requires SonarQube)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Bug count per file by GitHub Copilot adoption tier. Lower is better. Requires SonarQube integration.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 82 + }, + "id": 80, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly) SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS 'Bugs/File' FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "refId": "A" + } + ], + "title": "Bugs per File by Adoption", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Code smell count per file by GitHub Copilot adoption tier. Lower is better. Requires SonarQube integration.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 5 + }, + { + "color": "red", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 82 + }, + "id": 81, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS 'Code Smells/File' FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "refId": "A" + } + ], + "title": "Code Smells by Adoption", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Code complexity by GitHub Copilot adoption tier. Lower is better. Requires SonarQube integration.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 25 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 82 + }, + "id": 82, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS Complexity FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "refId": "A" + } + ], + "title": "Complexity by Adoption", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Code coverage percentage by GitHub Copilot adoption tier. Higher is better. Requires SonarQube integration.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "green", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 82 + }, + "id": 83, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS Coverage FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "refId": "A" + } + ], + "title": "Coverage by Adoption", + "type": "bargauge" + } + ], + "refresh": "", + "schemaVersion": 38, + "tags": [ + "copilot", + "devlake", + "impact", + "dora" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY 1", + "hide": 0, + "includeAll": false, + "label": "Connection ID", + "multi": false, + "name": "connection_id", + "options": [], + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS UNSIGNED) ORDER BY 1", + "hide": 0, + "includeAll": false, + "label": "Scope ID (Organization)", + "multi": false, + "name": "scope_id", + "options": [], + "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS UNSIGNED) ORDER BY 1", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1", + "hide": 0, + "includeAll": false, + "label": "Project", + "multi": false, + "name": "project", + "options": [], + "query": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [ + { + "selected": false, + "text": "2021", + "value": "2021" + }, + { + "selected": true, + "text": "2023", + "value": "2023" + } + ], + "query": "2021, 2023", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "GitHub Copilot + DORA Correlation (PostgreSQL)", + "uid": "copilot_impact-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json b/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json new file mode 100644 index 00000000000..43ed6fb03c9 --- /dev/null +++ b/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json @@ -0,0 +1,2712 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 22, + "iteration": 1682062991983, + "links": [], + "panels": [ + { + "datasource": null, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 57, + "links": [], + "options": { + "content": "- Use Cases: This dashboard can be used to track bugs.\n- Data Source Required: GitHub ([transformation](https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub#step-3---adding-transformation-rules-optional) required. Additional settings are required to get version data).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 45, + "panels": [], + "title": "Quality", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 7, + "x": 0, + "y": 4 + }, + "id": 15, + "links": [], + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the bug distribution in major versions */ WITH bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND i.type = 'BUG') SELECT CONCAT(SUBSTRING_INDEX(biet.tag_name, '.', 3), '.x') AS minor_version, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Bug Distribution [Minor Versions]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "Bug Count", + "axisPlacement": "left", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "cost_percentage(bugfixing commits/total commits)" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + { + "id": "custom.axisLabel", + "value": "Cost Percentage(%)" + }, + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "percentunit" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 17, + "x": 7, + "y": 4 + }, + "id": 29, + "links": [], + "options": { + "barWidth": 0.7, + "groupWidth": 0.3, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id /* distinct new_ref_id, old_ref_id */ FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs_of_tags AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, COUNT(*) AS bug_count /* SUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id, */ FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' GROUP BY 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE p.base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) AS repo_id, pr.id AS pull_request_id, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT SUBSTRING_INDEX(new_ref_id, ':', -1) FROM _last_5_tags) GROUP BY 1, 2, 3), _pr_worktype AS (SELECT DISTINCT pri.pull_request_id, i.type FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _pr_elco_and_worktype AS (SELECT ccop.tag_name, CAST(SUM(CASE WHEN pw.type = 'BUG' THEN commit_count ELSE 0 END) AS NUMERIC) / NULLIF(SUM(commit_count), 0) AS cost_percentage FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_worktype AS pw ON ccop.pull_request_id = pw.pull_request_id GROUP BY 1) SELECT bot.tag_name, bot.bug_count, peaw.cost_percentage AS \"cost_percentage(bugfixing commits/total commits)\" FROM _bugs_of_tags AS bot JOIN _pr_elco_and_worktype AS peaw ON bot.tag_name = peaw.tag_name ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Ratio of Bug Fix Commits [Last 5 Tags]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 11 + }, + "id": 55, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.2 Severity Distribution [Last Tag]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "dev_eq" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.3.2.2-lts UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.3.2.2-lts REQUIREMENT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.3.2.2-lts BUG" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 11 + }, + "id": 53, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.3 Severity Distribution [The Tag before Last]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 11 + }, + "id": 51, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.4 Severity Distribution [The 2nd Tag before Last]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "cost_percentage(bugfixing commits/total commits)" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 89 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 457 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "assignee_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 131 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "lead_time_in_days" + }, + "properties": [ + { + "id": "custom.width", + "value": 147 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "tag_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 147 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "repo_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 180 + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 43, + "links": [], + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT DISTINCT b.name AS repo_name, SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.issue_key AS issue_key, i.title, i.assignee_name, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id JOIN boards AS b ON SUBSTRING_INDEX(rid.new_ref_id, ':', 4) = b.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT SUBSTRING_INDEX(new_ref_id, ':', -1) FROM _last_5_tags) AND i.type = 'BUG' ORDER BY tag_name DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.5 List of Fixed Bugs [Last 5 Tags]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 11, + "x": 0, + "y": 25 + }, + "id": 30, + "links": [], + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT CASE WHEN component = '' THEN 'unlabeled' ELSE 'labeled' END AS component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Ratio of Issues with 'Component' Label [Last 5 Tags]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 13, + "x": 11, + "y": 25 + }, + "id": 31, + "links": [], + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet WHERE component <> '' GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.2 Source of Bugs by Component [Last 5 Tags]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.25 + }, + { + "color": "green", + "value": 0.4 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 32 + }, + "id": 23, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the % of contributors who fixed 80% of bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST), _bug_fixed_count AS (SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1), _bug_fixed_count_running_total AS (SELECT *, SUM(bug_fixed_count) OVER (ORDER BY bug_fixed_count DESC NULLS LAST) AS running_total FROM _bug_fixed_count), _percentile AS (SELECT pr_author, bug_fixed_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(bug_fixed_count) OVER (), 0) AS cumulative_percentage FROM _bug_fixed_count_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN pr_author ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"% of contributors who fixed 80% of the bugs\" FROM _percentile", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.1 Contributor Fixing 80%+ Bugs [Last 5 Tags]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "Bug Fixed Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 32 + }, + "id": 18, + "links": [], + "options": { + "barWidth": 0.5, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST) SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.2 Top Bug Fixers [Last 5 Tags]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 39 + }, + "id": 33, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the avg bug age in history */ SELECT CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.1 Mean Bug Age in Days [All History]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "Bug Fixed Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 2 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 20, + "x": 4, + "y": 39 + }, + "id": 32, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "barWidth": 0.7, + "groupWidth": 0.3, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the bug age in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.id, i.lead_time_minutes FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bugs_percentile AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY tag_name ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM _bugs ORDER BY 1 NULLS FIRST), _avg_bug_age AS (SELECT tag_name, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM _bugs_percentile GROUP BY 1), _50th_bug_age AS (SELECT tag_name, CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"50th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5 GROUP BY 1) SELECT aba.*, eba.\"50th_bug_age\" FROM _avg_bug_age AS aba JOIN _50th_bug_age AS eba ON aba.tag_name = eba.tag_name", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.2 Mean + Median Bug Age Days [Last 5 Tags]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 45 + }, + "id": 34, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the 80th perccentile bug age in history */ WITH _bugs_percentile AS (SELECT id, lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM issues WHERE type = 'BUG' AND status = 'DONE') SELECT CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"80th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.3 Median Bug Age in Days [All History]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "key" + }, + "properties": [ + { + "id": "custom.width", + "value": 109 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 725 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "custom.width", + "value": 182 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "lead_time_in_days" + }, + "properties": [ + { + "id": "custom.width", + "value": 136 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 79 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "custom.width", + "value": 784 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "tag_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 150 + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 20, + "x": 4, + "y": 45 + }, + "id": 38, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT b.name, SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.issue_key AS issue_key, i.title, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id JOIN boards AS b ON SUBSTRING_INDEX(rid.new_ref_id, ':', 4) = b.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bug_age_rank AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY tag_name ORDER BY lead_time_in_days DESC NULLS LAST) AS bug_age_rank FROM _bugs) SELECT name, tag_name, issue_key, title, lead_time_in_days, url FROM _bug_age_rank WHERE bug_age_rank <= 10 ORDER BY tag_name NULLS FIRST, lead_time_in_days DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.4 List of Long-Lead Bugs [Closed Bugs in Last 5 Tags]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "bug_count" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "=avg_bug_age" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 51 + }, + "id": 35, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "hidden", + "placement": "bottom", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN \">=avg_bug_age\" ELSE \" 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.5 Ratio of Outstanding Bugs [Queue Time > Mean]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "=avg_bug_age" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 81 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 535 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "created_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 149 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queue_time_in_days" + }, + "properties": [ + { + "id": "custom.width", + "value": 140 + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 51 + }, + "id": 39, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the queue time of all backlog bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(3600, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE i.type = 'BUG' AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[]) ORDER BY queue_time_in_days DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "5.6 List of Outstanding Bugs [All Open Bugs]", + "type": "table" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 57 + }, + "id": 47, + "panels": [], + "title": "Contribution", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 58 + }, + "id": 41, + "links": [], + "options": { + "barWidth": 0.3, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "horizontal", + "showValue": "auto", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rcd.old_ref_id, 'refs/tags/', -1) AS old_tag_name, COUNT(*) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2 ORDER BY 1 NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "6.1 Number of New Commits Per Tag", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "message" + }, + "properties": [ + { + "id": "custom.width", + "value": 395 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "additions" + }, + "properties": [ + { + "id": "custom.width", + "value": 92 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "deletions" + }, + "properties": [ + { + "id": "custom.width", + "value": 86 + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 58 + }, + "id": 42, + "links": [], + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'refs/tags/', -1) AS new_tag_name, SUBSTRING_INDEX(rcd.old_ref_id, 'refs/tags/', -1) AS compared_tag_name, c.sha, c.message, c.additions, c.deletions, c.author_name FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) ORDER BY 1 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "6.2 Commit List for Drill-Down [Last 5 Tags]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "v22.3.2.2-lts BUG" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.3.2.2-lts UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "text", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 65 + }, + "id": 26, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "7.1 Work-Type Distribution [Last Tag]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "v22.2.3.5-stable UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "text", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.2.3.5-stable BUG" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 65 + }, + "id": 36, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "7.2 Work-Type Distribution [The Tag before Last]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "v22.1.4.30-stable UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "text", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "v22.1.4.30-stable BUG" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 65 + }, + "id": 37, + "links": [], + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "7.3 Work-Type Distribution [The 2nd Tag before Last]", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "#EAB839", + "value": 0.25 + }, + { + "color": "green", + "value": 0.4 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 72 + }, + "id": 27, + "links": [], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "8.1 Committers Contributing 80%+ Commits [Last 5 Tags]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "Dev Equivalent", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 6, + "y": 72 + }, + "id": 3, + "links": [], + "options": { + "barWidth": 0.3, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "orientation": "auto", + "showValue": "auto", + "text": {}, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT c.author_name, COUNT(c.sha) AS total_dev_eq FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND SUBSTRING_INDEX(rcf.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "8.2 Top Contributors [Last 5 Tags]", + "type": "barchart" + }, + { + "datasource": null, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 79 + }, + "id": 59, + "options": { + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 30, + "style": "dark", + "tags": [ + "OSS Maintainer Dashboard" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "GitHub_Release_Quality_and_Contribution_Analysis (PostgreSQL)", + "uid": "2xuOaQUnk4-pg", + "version": 3 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Gitlab.json b/grafana/dashboards/postgresql/Gitlab.json new file mode 100644 index 00000000000..7a8f48cc3e3 --- /dev/null +++ b/grafana/dashboards/postgresql/Gitlab.json @@ -0,0 +1,2327 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 101, + "links": [ + { + "targetBlank": true, + "title": "GitLab", + "url": "https://devlake.apache.org/docs/Plugins/gitlab" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic Git and Code Review metrics from GitLab.\n- Data Source Required: GitLab", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 83, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Contribution (PRs)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 68, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Number of New Pull Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Pull Request Count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 4 + }, + "id": 77, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.2 Number of New Pull Requests", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Merged PR Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 59, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.3 Top 20 Contributors By Merged PRs", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 85, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. How Merge Requests(MRs) are handled?", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.05 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 17 + }, + "id": 66, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Ratio of unmerged MRs of All Closed or Merged PRs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "MR: Open" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "MR: Closed without merging" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "MR: Closed and merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 17 + }, + "id": 79, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"MR: Open\", closed AS \"MR: Closed without merging\", merged AS \"MR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.2 Merge Request Status Distribution", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 23 + }, + "id": 80, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.3 Number of MRs Closed without Merging", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Ratio", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 10, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 23 + }, + "id": 81, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.4 Ratio of unmerged MRs of All Closed or Merged MRs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 29 + }, + "id": 72, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.5 Mean Time to Merge of Merge Requests", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "dashed" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 29 + }, + "id": 95, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.6 Mean Time to Merge of MRs", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 110, + "panels": [], + "title": "CI/CD Metrics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Number of pipeline runs executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 36 + }, + "id": 102, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of successful pipeline runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful pipeline runs / Number of total pipeline runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 36 + }, + "id": 103, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.2 Mean pipeline run success rate %", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 108, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.25, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.3 Number of successful and failed pipeline runs", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 42 + }, + "id": 104, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.4 Pipeline runs result distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Pipeline Runs success rate over time.\n2. The Pipeline runs being calculated are filtered by \"workflow runs starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 42 + }, + "id": 107, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.5 Pipeline run success rate %", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of successful pipeline runs / Number of total pipeline runs", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 48 + }, + "id": 105, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.6 Mean pipeline runs duration", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_sec" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 18, + "x": 6, + "y": 48 + }, + "id": 109, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.25, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.7 Mean pipeline run duration", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 99, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from repos where id like 'gitlab%'", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '--', id) as text from repos where id like 'gitlab%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Month", + "value": "DAYOFMONTH" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "interval", + "options": [ + { + "selected": true, + "text": "Month", + "value": "DAYOFMONTH" + }, + { + "selected": false, + "text": "Week", + "value": "WEEKDAY" + } + ], + "query": "Month : DAYOFMONTH, Week : WEEKDAY", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "GitLab (PostgreSQL)", + "uid": "msSjEq97z-pg", + "version": 27, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Homepage.json b/grafana/dashboards/postgresql/Homepage.json new file mode 100644 index 00000000000..552f67440ea --- /dev/null +++ b/grafana/dashboards/postgresql/Homepage.json @@ -0,0 +1,310 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 26, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 32, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Welcome\n\n - Dashboards are grouped by use cases and data sources.\n - All data is queried from here. You can go to the 'Explore' page on the left menu to play around.\n - You can always make your own dashboard based on the domain layer schema, SQL examples in engineering metrics, and dashboard manuals.\n", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 22, + "options": { + "folderId": 0, + "maxItems": 10, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": false, + "showStarred": true, + "tags": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Starred Dashboards", + "type": "dashlist" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 2, + "options": { + "folderId": 0, + "maxItems": 50, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [ + "Highlights" + ] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Highlights", + "type": "dashlist" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 12, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "By Roles", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 41, + "options": { + "maxItems": 10, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [ + "Engineering Leads Dashboard" + ] + }, + "pluginVersion": "9.5.15", + "title": "For Engineering Leads", + "type": "dashlist" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 42, + "options": { + "maxItems": 10, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [ + "OSS Maintainer Dashboard" + ] + }, + "pluginVersion": "9.5.15", + "title": "For OSS Maintainers", + "type": "dashlist" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 43, + "options": { + "maxItems": 10, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [ + "Developer" + ] + }, + "pluginVersion": "9.5.15", + "title": "For Developers", + "type": "dashlist" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 14, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "By Data Sources", + "type": "row" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 14, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 40, + "options": { + "maxItems": 20, + "query": "", + "showHeadings": false, + "showRecentlyViewed": false, + "showSearch": true, + "showStarred": false, + "tags": [ + "Data Source Dashboard" + ] + }, + "pluginVersion": "9.5.15", + "type": "dashlist" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Homepage (PostgreSQL)", + "uid": "lCO8w-pVk-pg", + "version": 4, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Jenkins.json b/grafana/dashboards/postgresql/Jenkins.json new file mode 100644 index 00000000000..466eef95e0f --- /dev/null +++ b/grafana/dashboards/postgresql/Jenkins.json @@ -0,0 +1,1202 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 30, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 58, + "links": [ + { + "targetBlank": true, + "title": "Jenkins", + "url": "https://devlake.apache.org/docs/Plugins/jenkins" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic CI/CD metrics from Jenkins, such as [Build Count](https://devlake.apache.org/docs/Metrics/BuildCount), [Build Duration](https://devlake.apache.org/docs/Metrics/BuildDuration) and [Build Success Rate](https://devlake.apache.org/docs/Metrics/BuildSuccessRate).\n- Data Source Required: Jenkins", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "Number of builds executed in the selected time range", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1. Total Number of Successful Builds [Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of successful builds / Number of total builds", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2. Mean Build Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "build_count", + "ABORT" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SUCCESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FAILURE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ABORT" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(205, 204, 206, 1)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 3 + }, + "id": 37, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "displayLabels": [ + "value", + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3. Total Build Result Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Number of successful builds / Number of total builds", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-orange", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 3 + }, + "id": 55, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "refId": "A", + "select": [ + [ + { + "params": [ + "project_id" + ], + "type": "column" + } + ] + ], + "table": "gitlab_commits", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4. Mean Build Duration in Minutes", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 52, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1.1 Total Number of Successful Builds [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "1. Mean Build success rate over time.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Success Rate(%)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Build Success Rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 50, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Build Success Rate", + "url": "https://devlake.apache.org/docs/Metrics/BuildSuccessRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "2.1 Build Success Rate [Each Month]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Count", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 50, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successful_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "failed_build_count" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 54, + "links": [ + { + "targetBlank": true, + "title": "Build Count", + "url": "https://devlake.apache.org/docs/Metrics/BuildCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "3.1 Number of Successful and Failed Builds [Each Month]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Build Duration(minutes)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 60 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "mean_duration_minutes" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 56, + "links": [ + { + "targetBlank": true, + "title": "Build Duration", + "url": "https://devlake.apache.org/docs/Metrics/BuildDuration" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "4.1 Mean Build Duration in Minutes [Each Month]", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 60, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"jenkins%\" ", + "hide": 0, + "includeAll": true, + "label": "Job Name", + "multi": true, + "name": "job_id", + "options": [], + "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"jenkins%\" ", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Jenkins (PostgreSQL)", + "uid": "W8AiDFQnk-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Jira.json b/grafana/dashboards/postgresql/Jira.json new file mode 100644 index 00000000000..9d2aad195d2 --- /dev/null +++ b/grafana/dashboards/postgresql/Jira.json @@ -0,0 +1,1248 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 20, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Jira", + "url": "https://devlake.apache.org/docs/Plugins/jira" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic project management metrics from Jira.\n- Data Source Required: Jira", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of issues created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Delivered Issue [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Issues' Lead Time are less than # days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time [Issues Resolved in Select Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "1. The cumulative distribution of requirement lead time. \n2. Each point refers to the percent rank of a lead time.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 15, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.51, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Cumulative Distribution of Requirement Lead Time [Issues Resolved in Select Time Range]", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "current" + ] + } + } + ], + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'jira%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'jira%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "Jira (PostgreSQL)", + "uid": "F5vqBQl7z-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/KiroCreditsDORA.json b/grafana/dashboards/postgresql/KiroCreditsDORA.json new file mode 100644 index 00000000000..919483048f9 --- /dev/null +++ b/grafana/dashboards/postgresql/KiroCreditsDORA.json @@ -0,0 +1,709 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "DORA Dashboard", + "type": "link", + "url": "/d/qNo8_0M4z/dora" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Kiro Usage Dashboard", + "type": "link", + "url": "/d/qdev_user_report" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "## Kiro Credits + DORA Correlation\nThis dashboard correlates **Kiro AI usage (credits and messages)** with **DORA** performance indicators at a weekly aggregate level.\n\n- **Pearson's r** measures linear correlation: negative r suggests higher AI usage may correlate with shorter cycle times.\n- Data is aggregated by **week** and joined on `week_start`.", + "mode": "markdown" + }, + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 2, + "panels": [], + "title": "Overview", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation coefficient between weekly Kiro credits and PR cycle time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "Need more data" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": -0.3 + }, + { + "color": "orange", + "value": 0.3 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 4 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_hours FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, p.avg_cycle_hours AS y FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS 'r' FROM _stats AS s", + "refId": "A" + } + ], + "title": "Credits vs Cycle Time (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total Kiro credits consumed in period", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 8, + "y": 4 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(credits_used) AS 'Credits Used' FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Total Credits", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Median PR cycle time in hours", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 12, + "y": 4 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _ranked AS (SELECT CAST(pr_cycle_time AS NUMERIC) / NULLIF(60.0, 0) AS ct, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS prank FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date)) SELECT ROUND(MAX(ct), 1) AS 'Median Cycle Time' FROM _ranked WHERE prank <= 0.5", + "refId": "A" + } + ], + "title": "Median Cycle Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Cycle time comparison: weeks with above-median vs below-median AI credits usage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 4 + }, + "id": 6, + "options": { + "barRadius": 0.1, + "barWidth": 0.6, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_ct FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits, p.avg_ct FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _med AS (SELECT weekly_credits AS med FROM _joined ORDER BY weekly_credits NULLS FIRST LIMIT 1 OFFSET (SELECT FLOOR(CAST(COUNT(*) AS NUMERIC) / NULLIF(2, 0)) FROM _joined)) SELECT CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END AS 'Tier', ROUND(AVG(j.avg_ct), 1) AS 'Avg Cycle Time' FROM _joined AS j, _med AS m GROUP BY CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END", + "refId": "A" + } + ], + "title": "Cycle Time: High vs Low AI Usage", + "type": "bargauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 10, + "panels": [], + "title": "Weekly Trends", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly Kiro credits consumed", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 11 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS 'Credits Used', COUNT(DISTINCT user_id) AS 'Active Users' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly Kiro Credits & Active Users", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly average PR cycle time in hours", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS time, ROUND(CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS 'Avg Cycle Time (hrs)', COUNT(*) AS 'PRs Merged' FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly PR Cycle Time & Volume", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 20, + "panels": [], + "title": "Deployment Frequency", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Pearson correlation between weekly credits and deployment count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "Need more data" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 20 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "none", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, d.deploys AS y FROM _kiro_weekly AS k INNER JOIN _deploy_weekly AS d ON k.week_start = d.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS 'r' FROM _stats AS s", + "refId": "A" + } + ], + "title": "Credits vs Deploy Freq (r)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Weekly credits overlaid with deployment count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 18, + "x": 6, + "y": 20 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT time, SUM(credits) AS 'Credits Used', SUM(deploys) AS 'Deployments' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS deploys FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly Credits vs Deployments", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 30, + "panels": [], + "title": "Change Failure Rate", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Change failure rate: % of deployments that caused incidents", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 31, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT time, SUM(credits) AS 'Credits Used', SUM(cfr) AS 'Change Failure Rate' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS cfr FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, CAST(SUM(CASE WHEN NOT i.id IS NULL THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT d.deployment_id), 0) AS cfr FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY cdc.cicd_deployment_id) AS d LEFT JOIN project_incident_deployment_relationships AS pidr ON d.deployment_id = pidr.deployment_id LEFT JOIN incidents AS i ON pidr.id = i.id GROUP BY CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly Credits vs Change Failure Rate", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "dora", + "kiro", + "correlation" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT name FROM projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "SELECT DISTINCT name FROM projects", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Credits + DORA Correlation (PostgreSQL)", + "uid": "kiro_credits_dora-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/LanguageAIHeatmap.json b/grafana/dashboards/postgresql/LanguageAIHeatmap.json new file mode 100644 index 00000000000..467e0fa2d70 --- /dev/null +++ b/grafana/dashboards/postgresql/LanguageAIHeatmap.json @@ -0,0 +1,276 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Completion requests, avg context size, and completion rate per language", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Requests" + } + ] + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS 'Language', COUNT(*) AS 'Requests', ROUND(AVG(completions_count), 2) AS 'Avg Completions', ROUND(AVG(left_context_length)) AS 'Avg Left Context', ROUND(AVG(right_context_length)) AS 'Avg Right Context', ROUND(AVG(left_context_length + right_context_length)) AS 'Avg Total Context', COUNT(DISTINCT user_id) AS 'Users' FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Language Completion Profile", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Daily completion requests by language", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "bars", + "fillOpacity": 80, + "lineWidth": 1, + "stacking": { + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS metric, COUNT(*) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), file_extension ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Completions by Language", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Which file types are most active during chat sessions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 3, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file)' ELSE active_file_extension END AS 'File Type', COUNT(*) AS 'Chat Events' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "refId": "A" + } + ], + "title": "Active File Types During Chat", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Context size trends for top languages", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, file_extension AS metric, ROUND(AVG(left_context_length + right_context_length)) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) AND file_extension IN (SELECT file_extension FROM _tool_q_dev_completion_log GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 5) GROUP BY CAST(timestamp AS DATE), file_extension ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Avg Context Size by Language (Top 5)", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "language", + "completions" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Language AI Heatmap (PostgreSQL)", + "uid": "kiro_language_heatmap-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/MultiAIComparison.json b/grafana/dashboards/postgresql/MultiAIComparison.json new file mode 100644 index 00000000000..27c800329c8 --- /dev/null +++ b/grafana/dashboards/postgresql/MultiAIComparison.json @@ -0,0 +1,464 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Kiro Usage", + "type": "link", + "url": "/d/qdev_user_report" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Copilot Adoption", + "type": "link", + "url": "/d/copilot_adoption" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "## Multi-AI Tool Comparison: Copilot vs Kiro\nCompare GitHub Copilot and Kiro (Amazon Q Developer) adoption and usage side by side.\n\n**Note:** Copilot metrics come from `_tool_copilot_enterprise_daily_metrics` (enterprise-level aggregates). Kiro metrics come from `_tool_q_dev_user_report` and `_tool_q_dev_user_data`. Select the Copilot connection and scope using the dropdowns above.", + "mode": "markdown" + }, + "title": "About", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 2, + "panels": [], + "title": "Active Users", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly active users for both tools", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT user_id) AS 'Kiro Active Users' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + }, + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, MAX(daily_active_users) AS 'Copilot Active Users' FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "B" + } + ], + "title": "Weekly Active Users Comparison", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 10, + "panels": [], + "title": "Code Generation", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly code generation and acceptance events", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 13 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_suggestions_count) AS 'Kiro: Suggestions', SUM(inline_acceptance_count) AS 'Kiro: Accepted' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Kiro: Code Suggestions & Acceptance", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly Copilot code generation and acceptance events", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 13 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, SUM(code_generation_activity_count) AS 'Copilot: Suggestions', SUM(code_acceptance_activity_count) AS 'Copilot: Accepted' FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Copilot: Code Suggestions & Acceptance", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 20, + "panels": [], + "title": "Lines of Code & Acceptance Rate", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly AI-generated lines of code accepted", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT time, SUM(kiro_loc) AS 'Kiro LOC Accepted', SUM(copilot_loc) AS 'Copilot LOC Added' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_ai_code_lines + chat_ai_code_lines) AS kiro_loc, 0 AS copilot_loc FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, 0 AS kiro_loc, SUM(loc_added_sum) AS copilot_loc FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "LOC Accepted: Kiro vs Copilot", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Overall acceptance rates for both tools", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 22, + "options": { + "barRadius": 0.1, + "barWidth": 0.5, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'Kiro' AS Tool, ROUND(CAST(SUM(inline_acceptance_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0), 1) AS 'Acceptance Rate' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Copilot' AS Tool, ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day)", + "refId": "A" + } + ], + "title": "Acceptance Rate Comparison", + "type": "bargauge" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "copilot", + "kiro", + "comparison" + ], + "templating": { + "list": [ + { + "current": {}, + "datasource": "postgresql", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "hide": 0, + "label": "Copilot Connection", + "name": "copilot_connection_id", + "options": [], + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "refresh": 1, + "type": "query" + }, + { + "current": {}, + "datasource": "postgresql", + "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS UNSIGNED)", + "hide": 0, + "label": "Copilot Scope", + "name": "copilot_scope_id", + "options": [], + "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS UNSIGNED)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Multi-AI Tool Comparison (PostgreSQL)", + "uid": "multi_ai_comparison-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Opsgenie.json b/grafana/dashboards/postgresql/Opsgenie.json new file mode 100644 index 00000000000..5c7ec438b5a --- /dev/null +++ b/grafana/dashboards/postgresql/Opsgenie.json @@ -0,0 +1,1536 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 12, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Opsgenie", + "url": "https://devlake.apache.org/docs/Plugins/opsgenie" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the incident data from Opsgenie.\n- Data Source Required: Opsgenie", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Incident Resolution Status", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of incidents created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Opsgenie board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Incidents [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Resolved Incidents [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "1. Total number of incidents created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "A" + }, + "properties": [ + { + "id": "custom.filterable", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 246 + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 131, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.title AS \"Title\", i.issue_key AS \"Id\", STRING_AGG(b.name, \", \") AS \"Service(s)\", i.description AS \"Description\", i.original_status AS \"Original Status\", i.priority AS \"Priority\", i.created_date AS \"Created Date\", i.updated_date AS \"Updated Date\", ROUND((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)), 1) AS \"Lead Time Days\", i.url AS \"URL\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY i.title, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, i.lead_time_minutes, i.url", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "List of Incidents [Created in Selected Time Range]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 7 + }, + "id": 134, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'closed' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Closed Incidents [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Incident Resolution Rate [Incidents created in the selected time range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Resolution Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Incident Resolution Rate over Time [Incidents Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Mean Time to Resolve (MTTR)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "MTTR [Incidents Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Incidents' MTTR are less than # [Incidents Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Incident Age(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean MTTR [Incidents Resolved in Select Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "1. The cumulative distribution of MTTR\n2. Each point refers to the percent rank of a distinct duration to resolve incidents.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.2", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "$$hashKey": "object:469", + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of MTTR [Incidents Resolved in Select Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.2", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 132, + "panels": [], + "title": "3. Incident Responders", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Due to Opsgenie API limitation regarding the relationship between assignee, users, and incident (and lack of 'assign at' key ), the column assignee represents a JSON object containing all assignee from said incident", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "json-view" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "issue_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 504 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "custom.width", + "value": 286 + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 133, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT i.title AS \"Incident Name\", CONCAT('[', STRING_AGG(CONCAT('{\"name\": \"', a.assignee_name, '\"', ', \"type\": \"', r.type, '\"}'), \", \"), ']') AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (SUBSTRING_INDEX(i.id, \":\", -1) = SUBSTRING_INDEX(a.issue_id, \":\", -1)) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "List of Incident Responder [organized in JSON format]", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'opsgenie%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'opsgenie%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "2021-06-05T21:00:42.390Z", + "to": "2025-06-16T21:00:42.390Z" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Opsgenie (PostgreSQL)", + "uid": "b4556439-f173-4411-93d4-65f261726d24-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/PagerDuty.json b/grafana/dashboards/postgresql/PagerDuty.json new file mode 100644 index 00000000000..f8120b952b8 --- /dev/null +++ b/grafana/dashboards/postgresql/PagerDuty.json @@ -0,0 +1,1295 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 20, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "PagerDuty", + "url": "https://devlake.apache.org/docs/Plugins/pagerduty" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the incident data from PagerDuty.\n- Data Source Required: PagerDuty", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Incident Resolution Status", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of incidents created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Incidents [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Resolved Incidents [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "1. Total number of incidents created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "A" + }, + "properties": [ + { + "id": "custom.filterable", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 131, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT b.name AS service, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, ROUND((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)), 1) AS lead_time_days, i.url FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "List of Incidents [Created in Selected Time Range]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Incident Resolution Rate [Incidents created in the selected time range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Resolution Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Incident Resolution Rate over Time [Incidents Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Mean Time to Resolve (MTTR)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "MTTR [Incidents Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Incidents' MTTR are less than # [Incidents Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Incident Age(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean MTTR [Incidents Resolved in Select Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "1. The cumulative distribution of MTTR\n2. Each point refers to the percent rank of a distinct duration to resolve incidents.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Incident Age", + "url": "https://devlake.apache.org/docs/Metrics/IncidentAge" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.15", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "$$hashKey": "object:469", + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of MTTR [Incidents Resolved in Select Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'pagerduty%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'pagerduty%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "PagerDuty (PostgreSQL)", + "uid": "abb26d07-3268-4bdf-b871-6e39b37b9e00-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/QDevDORA.json b/grafana/dashboards/postgresql/QDevDORA.json new file mode 100644 index 00000000000..7391dad6eb6 --- /dev/null +++ b/grafana/dashboards/postgresql/QDevDORA.json @@ -0,0 +1,1233 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "DORA Dashboard", + "tooltip": "", + "type": "link", + "url": "/d/qNo8_0M4z/dora" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Q Dev Dashboard", + "tooltip": "", + "type": "link", + "url": "/d/qdev_user_data/q-dev-user-data-dashboard" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "## AI-Powered DORA Dashboard\nThis dashboard correlates **Q Dev (AI coding assistant)** usage metrics with **DORA** performance indicators to help understand the impact of AI-assisted development on engineering efficiency.\n\n- **Left side**: Q Dev AI usage metrics (code generation, acceptance rate)\n- **Right side**: DORA metrics (Lead Time, Deployment Frequency, Change Failure Rate)\n- **Correlation charts**: Show trends over time to identify potential relationships", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 2, + "panels": [], + "title": "Overview Statistics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Number of unique users who used Q Dev AI features", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Q Dev Users' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Q Dev Active Users", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total AI-generated code lines accepted (Inline + Chat)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(inline_ai_code_lines + chat_ai_code_lines) AS 'AI Accepted Lines' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Total AI Code Lines", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Acceptance rate of inline AI suggestions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.3 + }, + { + "color": "green", + "value": 0.5 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 4 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Acceptance Rate' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "AI Acceptance Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Number of production deployments in selected period", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 4 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS 'Deployments' FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", + "refId": "A" + } + ], + "title": "Total Deployments", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Median lead time for changes in hours", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 24 + }, + { + "color": "red", + "value": 168 + } + ] + }, + "unit": "h" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 4 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0), 1) AS 'Lead Time (hours)' FROM _median_ranks WHERE ranks <= 0.5", + "refId": "A" + } + ], + "title": "Median Lead Time", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage of deployments that caused incidents", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.1 + }, + { + "color": "red", + "value": 0.15 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 4 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS 'Change Failure Rate' FROM _failure_caused", + "refId": "A" + } + ], + "title": "Change Failure Rate", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 9, + "panels": [], + "title": "AI Usage vs DORA Metrics Correlation", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Compare AI code generation trends with Lead Time for Changes. A negative correlation (AI lines up, Lead Time down) suggests AI is helping accelerate delivery.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "AI Accepted Lines" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "custom.axisLabel", + "value": "AI Code Lines" + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Median Lead Time (hours)" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Lead Time (hours)" + }, + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), lead_time_monthly AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS 'AI Accepted Lines', ROUND(lt.avg_lead_time, 1) AS 'Median Lead Time (hours)' FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "AI Code Generation vs Lead Time Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Compare AI suggestion acceptance rate with deployment frequency. Higher acceptance rate may indicate better AI integration and potentially more deployments.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "AI Acceptance Rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "custom.axisLabel", + "value": "Acceptance Rate" + }, + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Deployment Count" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Deployments" + }, + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), deployment_count AS (SELECT TO_CHAR(MAX(cdc.finished_date), '%Y-%m-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS 'AI Acceptance Rate', dc.deploy_count AS 'Deployment Count' FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "AI Acceptance Rate vs Deployment Frequency", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Compare AI-generated tests with Change Failure Rate. More AI-generated tests might correlate with lower failure rates.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "AI Generated Tests" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "custom.axisLabel", + "value": "Tests Generated" + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Change Failure Rate" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Failure Rate" + }, + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), cfr_monthly AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS 'AI Generated Tests', cfr.cfr AS 'Change Failure Rate' FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "AI Test Generation vs Change Failure Rate", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Compare active Q Dev users with Code Review findings. More AI-assisted code review might catch issues earlier.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Active Users" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "custom.axisLabel", + "value": "Users" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Code Review Findings" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Findings" + }, + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT TO_DATE(TO_CHAR(date, '%Y-%m-01'), '%Y-%m-%d') AS time, COUNT(DISTINCT user_id) AS 'Active Users', SUM(code_review_findings_count) AS 'Code Review Findings' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01') ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Q Dev Users vs Code Review Findings", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 14, + "panels": [], + "title": "Monthly Comparison Table", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Monthly summary comparing Q Dev AI metrics with DORA metrics side by side", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "AI Acceptance Rate" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + }, + { + "id": "color", + "value": { + "mode": "continuous-GrYlRd" + } + }, + { + "id": "max", + "value": 1 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Change Failure Rate" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + }, + { + "id": "color", + "value": { + "mode": "continuous-RdYlGr" + } + }, + { + "id": "max", + "value": 0.3 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Lead Time (hours)" + }, + "properties": [ + { + "id": "unit", + "value": "h" + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 15, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Month" + } + ] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(date, '%Y-%m') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m')), dora_metrics AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m')), cfr_metrics AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS 'Month', COALESCE(ai.active_users, 0) AS 'Q Dev Users', COALESCE(ai.ai_lines, 0) AS 'AI Code Lines', ai.acceptance_rate AS 'AI Acceptance Rate', COALESCE(ai.generated_tests, 0) AS 'AI Tests', COALESCE(ai.review_findings, 0) AS 'Review Findings', COALESCE(dm.deployments, 0) AS 'Deployments', ROUND(dm.avg_lead_time, 1) AS 'Lead Time (hours)', cfr.cfr AS 'Change Failure Rate' FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Monthly Q Dev vs DORA Metrics Comparison", + "type": "table" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 39, + "tags": [ + "q_dev", + "DORA", + "AI", + "correlation" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT name FROM projects", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "SELECT DISTINCT name FROM projects", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Q Dev + DORA Correlation (PostgreSQL)", + "uid": "qdev_dora_correlation-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/SonarQubeCloud.json b/grafana/dashboards/postgresql/SonarQubeCloud.json new file mode 100644 index 00000000000..60253103def --- /dev/null +++ b/grafana/dashboards/postgresql/SonarQubeCloud.json @@ -0,0 +1,1503 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 40, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 19, + "links": [ + { + "targetBlank": true, + "title": "SonarQube", + "url": "https://devlake.apache.org/docs/Plugins/sonarqube" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the code quality metrics from SonarCloud.\n- Data Source Required: SonarCloud\n- This dashboard does not honor the time filter on the top-right side as SonarQube metrics are all from the latest scan.", + "mode": "markdown" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 16, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Software Quality", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 5 + }, + "id": 3, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'SECURITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Security", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 5 + }, + "id": 2, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'RELIABILITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Reliability", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 5 + }, + "id": 20, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'MAINTAINABILITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Maintainability", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 21, + "panels": [], + "title": "Security Review", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 11 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Security Hotspots' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Security Hotspots", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 11 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS 'Reviewed' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Hotspots Reviewed", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 12, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Test & Maintainability", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 8, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Test", + "url": "https://devlake.apache.org/docs/Metrics/CQTest" + } + ], + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1), '% ', 'Coverage on ', ROUND(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines to cover') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Test Coverage", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "code smells" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 17 + }, + "id": 14, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Maintainability-Debt", + "url": "https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Code Smells' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Maintainability - Code Smells", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "string" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 17 + }, + "id": 7, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Maintainability-Debt", + "url": "https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS 'Debt' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Maintainability - Debt", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 6, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Duplications", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 23 + }, + "id": 10, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Blocks", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Duplicated Blocks", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 23 + }, + "id": 9, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Lines", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedLines" + } + ], + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1), '% ', 'Duplications on ', ROUND(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Duplicated Lines", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 22, + "panels": [], + "title": "Size", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 29 + }, + "id": 23, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Blocks", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(ncloc) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Lines of Code", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 29 + }, + "id": 24, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Blocks", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(num_of_lines) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Lines", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 29 + }, + "id": 25, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Blocks", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT file_path) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Files", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 26, + "panels": [], + "title": "Overall Code Quality Metrics", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "color-text" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 17, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "dataset": "lake", + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT file_name, num_of_lines AS 'Lines of Code', bugs AS 'Bugs', vulnerabilities AS 'Vulnerabilities', code_smells AS 'Code Smells', security_hotspots AS 'Security Hotspots', CONCAT(ROUND(coverage, 2), '%') AS 'Coverage', CONCAT(ROUND(duplicated_lines_density, 2), '%') AS 'Duplications' FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Code Quality Metrics by Files (Top 20 order by Bugs)", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from cq_projects", + "hide": 0, + "includeAll": true, + "label": "SonarQube Project", + "multi": true, + "name": "project_id", + "options": [], + "query": "select concat(name, '--', id) as text from cq_projects", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct severity from cq_issues where id like 'sonar%'", + "hide": 0, + "includeAll": true, + "label": "Severity", + "multi": true, + "name": "severity", + "options": [], + "query": "select distinct severity from cq_issues where id like 'sonar%'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "SonarQube Cloud (PostgreSQL)", + "uid": "WA0qbuJ4l-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Sonarqube.json b/grafana/dashboards/postgresql/Sonarqube.json new file mode 100644 index 00000000000..d8c92da978b --- /dev/null +++ b/grafana/dashboards/postgresql/Sonarqube.json @@ -0,0 +1,1098 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 19, + "links": [ + { + "targetBlank": true, + "title": "SonarQube", + "url": "https://devlake.apache.org/docs/Plugins/sonarqube" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the code quality metrics from SonarQube.\n- Data Source Required: SonarQube v8.2+\n- This dashboard does not honor the time filter on the top-right side as SonarQube metrics are all from the latest scan.", + "mode": "markdown" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 16, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Reliability & Security", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bugs" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 2, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Bugs' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'BUG' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Reliability", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "vulnerabilities" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 3, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Vulnerabilities' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'VULNERABILITY' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Security", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "hotspots" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Security Hotspots' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Security Hotspots", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS 'Reviewed' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Security Reviewed", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 12, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Test & Maintainability", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 10 + }, + "id": 8, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Test", + "url": "https://devlake.apache.org/docs/Metrics/CQTest" + } + ], + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1), '% ', 'Coverage on ', ROUND(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines to cover') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Test", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "string" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 10 + }, + "id": 7, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Maintainability-Debt", + "url": "https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS 'Debt' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Maintainability - Debt", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "code smells" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 10 + }, + "id": 14, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Maintainability-Debt", + "url": "https://devlake.apache.org/docs/Metrics/CQMaintainability-Debt" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT id) AS 'Code Smells' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Maintainability - Code Smells", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 6, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Duplication & Code", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 10, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Blocks", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedBlocks" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Duplicated Blocks", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 9, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Duplicated Lines", + "url": "https://devlake.apache.org/docs/Metrics/CQDuplicatedLines" + } + ], + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/.*/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CONCAT(ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1), '% ', 'Duplications on ', ROUND(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Duplicated Lines", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "color-text" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 17, + "links": [ + { + "targetBlank": true, + "title": "Code Quality Issue Count", + "url": "https://devlake.apache.org/docs/Metrics/CQIssueCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT file_name, num_of_lines AS 'Lines of Code', bugs AS 'Bugs', vulnerabilities AS 'Vulnerabilities', code_smells AS 'Code Smells', security_hotspots AS 'Security Hotspots', CONCAT(ROUND(coverage, 2), '%') AS 'Coverage', CONCAT(ROUND(duplicated_lines_density, 2), '%') AS 'Duplications' FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Code Quality Metrics by Files (Top 20 order by Bugs)", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Data Source Dashboard", + "Stable Data Sources" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) as text from cq_projects", + "hide": 0, + "includeAll": true, + "label": "SonarQube Project", + "multi": true, + "name": "project_id", + "options": [], + "query": "select concat(name, '--', id) as text from cq_projects", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct severity from cq_issues where id like 'sonar%'", + "hide": 0, + "includeAll": true, + "label": "Severity", + "multi": true, + "name": "severity", + "options": [], + "query": "select distinct severity from cq_issues where id like 'sonar%'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "SonarQube Server (PostgreSQL)", + "uid": "WA0qbuJ4k-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/SteeringAdoptionTracker.json b/grafana/dashboards/postgresql/SteeringAdoptionTracker.json new file mode 100644 index 00000000000..a774b93949a --- /dev/null +++ b/grafana/dashboards/postgresql/SteeringAdoptionTracker.json @@ -0,0 +1,353 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Users with Steering", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN is_spec_mode = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Users with Spec Mode", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Requests with Steering", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Weekly trend of steering and spec mode adoption rate", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS 'Steering Rate', CAST(SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS 'Spec Mode Rate' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly Steering & Spec Mode Adoption Rate", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Compare prompt and response quality between steering and non-steering sessions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 5, + "options": { + "barRadius": 0.1, + "barWidth": 0.5, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'With Steering' AS 'Mode', ROUND(AVG(prompt_length)) AS 'Avg Prompt', ROUND(AVG(response_length)) AS 'Avg Response' FROM _tool_q_dev_chat_log WHERE has_steering = TRUE AND $__timeFilter(timestamp) UNION ALL SELECT 'Without Steering', ROUND(AVG(prompt_length)), ROUND(AVG(response_length)) FROM _tool_q_dev_chat_log WHERE has_steering = FALSE AND $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Steering Impact: Prompt & Response Length", + "type": "bargauge" + }, + { + "datasource": "postgresql", + "description": "Per-user steering and spec mode usage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 6, + "options": { + "cellHeight": "sm", + "showHeader": true + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', COUNT(*) AS 'Total Chats', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode', ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS 'Steering %' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Per-User Feature Adoption", + "type": "table" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "steering", + "adoption" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Steering & Spec Mode Adoption (PostgreSQL)", + "uid": "kiro_steering_adoption-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/TAPD.json b/grafana/dashboards/postgresql/TAPD.json new file mode 100644 index 00000000000..697c5bb4354 --- /dev/null +++ b/grafana/dashboards/postgresql/TAPD.json @@ -0,0 +1,1195 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 13, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "TAPD", + "url": "https://devlake.apache.org/docs/Plugins/tapd" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic project management metrics from TAPD.\n- Data Source Required: TAPD", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of issues created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Delivered Issue [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Issues' Lead Time are less than # days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time [Issues Resolved in Select Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "1. The cumulative distribution of requirement lead time. \n2. Each point refers to the percent rank of a lead time.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.15", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "$$hashKey": "object:469", + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of Requirement Lead Time [Issues Resolved in Select Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'tapd%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'tapd%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "TAPD (PostgreSQL)", + "uid": "hi-907hVk-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Taiga.json b/grafana/dashboards/postgresql/Taiga.json new file mode 100644 index 00000000000..60205c69be0 --- /dev/null +++ b/grafana/dashboards/postgresql/Taiga.json @@ -0,0 +1,2442 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [ + { + "targetBlank": true, + "title": "Taiga Plugin", + "url": "https://github.com/apache/incubator-devlake" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows project management metrics from Taiga across all issue types (User Stories, Tasks, Issues/Requirements, Epics).\n- Data Source Required: Taiga\n- Metrics: Issue throughput (filtered by type), lead time, story points (User Stories), milestone progress, assignee breakdown and issue type breakdown.\n- Use the **Issue Type** variable to filter panels to a specific type, or select All to see all types together.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 100, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Total number of user stories created in the selected time range and board.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 2, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total number of issues that have been marked as Done/Closed in the selected time range.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 3, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Closed Issues [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Monthly distribution of open vs. closed issues.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Stories\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Closed Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Closed Issues) / count(All Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 5, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Monthly trend of issue delivery rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 101, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Average number of days from story creation to resolution.", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 7, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Lead Time in Days [Resolved in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "80% of stories are resolved within this many days.", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 8, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Stories' Lead Time \u2264 # Days [Resolved in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Mean lead time per month for resolved stories.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time (days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 9, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Lead Time by Month [Resolved in Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "The cumulative distribution of story lead time. Each point shows the percent rank of a given lead time value.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 10, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.51, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Cumulative Distribution of Lead Time [Resolved in Selected Time Range]", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "current" + ] + } + } + ], + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 102, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "3. Story Points", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Total story points for all user stories in the selected time range.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 30 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Story Points", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Total story points for closed/completed user stories.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 30 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND i.status = 'DONE'", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Completed Story Points", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Story points allocated to each milestone/sprint. Requires milestone data from Taiga.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Story Points", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 30 + }, + "id": 13, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": -15, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, SUM(ts.total_points) AS \"Story Points\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Story Points by Milestone", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Story points distributed across assignees.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Story Points", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 14, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": -15, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS Assignee, SUM(i.story_point) AS \"Story Points\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Story Points by Assignee [Created in Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Distribution of story points across open vs. closed stories.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 15, + "options": { + "displayLabels": [ + "name", + "value" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "pie", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT SUM(ts.total_points) AS \"Story Points\", CASE WHEN i.status = 'DONE' THEN 'Completed' ELSE 'In Progress' END AS Status FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY Status", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Story Points: Completed vs In Progress", + "type": "piechart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 103, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "4. Issues Overview", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Breakdown of all Taiga issues by their current status, across all issue types (User Story, Task, Issue/Requirement, Epic).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 16, + "options": { + "displayLabels": [ + "name", + "value" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "donut", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS 'To Do', COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS 'In Progress', COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS 'Done', COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS 'Other' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A" + } + ], + "title": "Issues by Status", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Number of user stories assigned to each team member.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "User Stories", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 17, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS Assignee, COUNT(DISTINCT i.id) AS \"User Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issues by Assignee [Created in Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Number of user stories in each milestone/sprint.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "User Stories", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 18, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, COUNT(DISTINCT ts.user_story_id) AS \"User Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "User Stories per Milestone", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Milestone progress: open vs. closed user stories per sprint.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 51 + }, + "id": 19, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": -15, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Open Stories\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Closed Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Closed Stories\" DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Milestone Progress: Open vs. Closed Stories", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 60 + }, + "id": 104, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "5. Issue Type Breakdown", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Distribution of all Taiga items across issue types (User Story, Task, Issue/Requirement, Epic).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 61 + }, + "id": 20, + "options": { + "displayLabels": [ + "name", + "value" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "pie", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS 'User Story', COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS 'Task', COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS 'Issue', COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS 'Epic' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A" + } + ], + "title": "Issues by Type", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Open vs closed count for each Taiga issue type in the selected time range.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 6, + "y": 61 + }, + "id": 21, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN i.id ELSE NULL END) AS \"Open\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Closed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Open vs Closed by Type [Created in Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Monthly count of items created, broken down by issue type (User Stories, Tasks, Issues, Epics).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 61 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS 'User Stories', COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS 'Tasks', COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS 'Issues', COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS 'Epics' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "refId": "A" + } + ], + "title": "Items Created per Month by Type", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Average lead time in days from creation to resolution, broken down by issue type (resolved issues only).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Avg Lead Time (days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 69 + }, + "id": 23, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", ROUND(AVG(CAST(COALESCE(i.lead_time_minutes, (EXTRACT(EPOCH FROM (i.resolution_date - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)), 1) AS \"Avg Lead Time (days)\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.status = 'DONE' AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 HAVING NOT \"Avg Lead Time (days)\" IS NULL ORDER BY 2 DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Average Lead Time by Type", + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'taiga%'", + "hide": 0, + "includeAll": true, + "label": "Choose Project", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'taiga%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct i.type from issues i join board_issues bi on i.id = bi.issue_id where bi.board_id like 'taiga%'", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": true, + "name": "type", + "options": [], + "query": "select distinct i.type from issues i join board_issues bi on i.id = bi.issue_id where bi.board_id like 'taiga%'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Taiga (PostgreSQL)", + "uid": "taiga-dashboard-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Teambition.json b/grafana/dashboards/postgresql/Teambition.json new file mode 100644 index 00000000000..ce93e8b1c8d --- /dev/null +++ b/grafana/dashboards/postgresql/Teambition.json @@ -0,0 +1,1195 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 6, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Teambition", + "url": "https://devlake.apache.org/docs/Plugins/teambition" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic project management metrics from Teambition.\n- Data Source Required: Teambition", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of issues created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Delivered Issue [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Issues' Lead Time are less than # days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time [Issues Resolved in Select Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "1. The cumulative distribution of requirement lead time. \n2. Each point refers to the percent rank of a lead time.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.15", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "$$hashKey": "object:469", + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of Requirement Lead Time [Issues Resolved in Select Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'teambition%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'teambition%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Teambition (PostgreSQL)", + "uid": "hi-908hVk-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Testmo.json b/grafana/dashboards/postgresql/Testmo.json new file mode 100644 index 00000000000..da56d9b5c51 --- /dev/null +++ b/grafana/dashboards/postgresql/Testmo.json @@ -0,0 +1,1232 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Testmo", + "url": "https://www.testmo.com/" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows comprehensive test management metrics from Testmo.\n- Data Source Required: Testmo\n- Metrics: Test execution, success rates, coverage, and performance analytics", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Test Execution Overview", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Total number of test runs executed in the selected time range", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(*) AS value FROM (SELECT DISTINCT ar.id, ar.connection_id, ar.testmo_created_at FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT DISTINCT r.id, r.connection_id, r.testmo_created_at FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "refId": "A" + } + ], + "title": "Total Test Runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage of test runs that passed successfully", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 70 + }, + { + "color": "green", + "value": 90 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS value FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "refId": "A" + } + ], + "title": "Test Success Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Test execution results over time showing pass/fail/skip distribution", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Passed Tests" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "green" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Failed Tests" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "red" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Skipped Tests" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "yellow" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, COUNT(CASE WHEN combined.status = 1 THEN 1 ELSE NULL END) AS \"Passed Tests\", COUNT(CASE WHEN combined.status = 2 THEN 1 ELSE NULL END) AS \"Failed Tests\", COUNT(CASE WHEN NOT combined.status IN (1, 2) THEN 1 ELSE NULL END) AS \"Other Tests\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Test Execution Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Total number of Testmo runs (new data source)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 10 + }, + "id": 117, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT r.id) AS value FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "refId": "A" + } + ], + "title": "Testmo Runs", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Average execution time per test run in minutes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 10 + }, + "id": 118, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT ar.id) AS value FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "refId": "A" + } + ], + "title": "Test Runs in Period", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Daily count of test runs from the new runs data source", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Number of Runs", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short", + "decimals": 0 + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Test Runs" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "purple" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 10 + }, + "id": 121, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(*) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Test Runs", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Test coverage across different projects", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Number of Tests", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 10 + }, + "id": 122, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT p.name AS metric, COUNT(*) AS \"Test Runs\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY p.name ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "refId": "A" + } + ], + "title": "Tests by Project", + "type": "barchart" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Test Performance & Quality", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Success rate trend over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Success Rate (%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 123, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Success Rate\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Test Success Rate Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Average test execution time trend", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Duration (minutes)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 124, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Test Count\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Test Count Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Comparison between automation runs and regular test runs over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Number of Runs", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 8, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Automation Runs" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "blue" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Test Runs" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "orange" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 131, + "options": { + "legend": { + "calcs": [ + "sum", + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Automation Runs\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "A" + }, + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(DISTINCT r.id) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "refId": "B" + } + ], + "title": "Automation Runs vs Test Runs Comparison", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Test runs that take the longest time to execute", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "m" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "unit", + "value": "m" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-background" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 30 + } + ] + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 125, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT combined.id AS \"Test Run ID\", p.name AS \"Project\", combined.name AS \"Test Name\", combined.status_name AS \"Status\", combined.testmo_created_at AS \"Created Date\", combined.source AS \"Source\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id, ar.name, CASE WHEN ar.status = 1 THEN 'Failed' WHEN ar.status = 2 THEN 'Passed' ELSE 'Other' END AS status_name, 'Automation Run' AS source FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id, r.name, r.status_name, 'Test Run' AS source FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) ORDER BY combined.testmo_created_at DESC NULLS LAST LIMIT 20", + "refId": "A" + } + ], + "title": "Recent Test Runs", + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from _tool_testmo_connections", + "hide": 0, + "includeAll": true, + "label": "Choose Connection", + "multi": true, + "name": "connection_id", + "options": [], + "query": "select concat(name, '--', id) from _tool_testmo_connections", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct name from _tool_testmo_projects where connection_id in (${connection_id})", + "hide": 0, + "includeAll": true, + "label": "Project", + "multi": true, + "name": "project", + "options": [], + "query": "select distinct name from _tool_testmo_projects where connection_id in (${connection_id})", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Testmo (PostgreSQL)", + "uid": "testmo-dashboard-pg", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/WeeklyBugRetro.json b/grafana/dashboards/postgresql/WeeklyBugRetro.json new file mode 100644 index 00000000000..31e4716d3c8 --- /dev/null +++ b/grafana/dashboards/postgresql/WeeklyBugRetro.json @@ -0,0 +1,2274 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 49, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 28, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard can be used to track bugs with metrics such as [Bug Age](https://devlake.apache.org/docs/Metrics/BugAge).\n- Data Source Required: One of the issue tracking tools, e.g. [GitHub](https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub#step-3---adding-transformation-rules-optional), [GitLab](https://devlake.apache.org/docs/Configuration/GitLab) or [Jira](https://devlake.apache.org/docs/UserManuals/ConfigUI/Jira#step-3---adding-transformation-rules-optional) (Scope Config required to define which issues are bugs).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 32, + "panels": [], + "title": "Overall Bug Count", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 46, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 2, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.71, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS 'Weekly New Bugs' FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Weekly New Bugs [Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 46, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 20, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.71, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS 'Weekly Closed Bugs' FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Weekly Closed Bugs [Selected Time Range]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 46, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 21, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.71, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST), created_bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), resolved_bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), weekly_new_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_new_bug FROM calendar_weeks AS cw LEFT JOIN created_bugs AS cb ON cw.start_of_week = cb.time), weekly_closed_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_closed_bug FROM calendar_weeks AS cw LEFT JOIN resolved_bugs AS cb ON cw.start_of_week = cb.time), weekly_updates AS (SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 LEFT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week UNION SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 RIGHT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week), original_open_bugs AS (SELECT COUNT(DISTINCT i.id) AS original_open_bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND i.created_date < $__timeFrom() AND (i.status <> 'DONE' OR $__timeFilter(i.resolution_date)) AND b.id = ANY(ARRAY[${board_id}]::text[])), weekly_updated_without_null AS (SELECT week, COALESCE(weekly_new_bug, 0) AS weekly_new_bug, COALESCE(weekly_closed_bug, 0) AS weekly_closed_bug, original_open_bug_count FROM weekly_updates, original_open_bugs WHERE NOT week IS NULL), weekly_delta AS (SELECT *, (weekly_new_bug - weekly_closed_bug) AS weekly_delta FROM weekly_updated_without_null ORDER BY week ASC NULLS FIRST), final_data AS (SELECT *, CONCAT(TO_CHAR(week, '%m/%d'), ' - ', TO_CHAR(week + INTERVAL '7 DAY', '%m/%d')) AS _week, SUM(weekly_delta) OVER (ORDER BY week ASC NULLS FIRST) AS weekly_accumulated FROM weekly_delta) SELECT _week, (original_open_bug_count + weekly_accumulated) AS \"Total No. of Outstanding Bugs By the End of Week\" FROM final_data", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Outstanding Bug Count by Week [Selected Time Range]", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 33 + }, + "id": 33, + "panels": [], + "title": "Detailed Bug Info", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 34 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Last Week's New Bug Count", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 34 + }, + "id": 26, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "displayLabels": [ + "name", + "value" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS 'Issue Number' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Last Week's New Bug Count by Priority", + "type": "piechart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Issue Number" + }, + "properties": [ + { + "id": "custom.width", + "value": 124 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 640 + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.Url}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Url" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 34 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', i.url AS 'Url', i.creator_name AS 'Creator' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Last Week's New Bugs", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 42 + }, + "id": 8, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Last Week's Fixed Bugs Count", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 14 + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 42 + }, + "id": 24, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Average Lead Time of Last Week's Resolved Bugs in Days", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Issue Number" + }, + "properties": [ + { + "id": "custom.width", + "value": 125 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 639 + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.Url}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Url" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Lead Time in Days" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 12, + "x": 12, + "y": 42 + }, + "id": 10, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS 'Lead Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Last Week's Fixed Bugs", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 50 + }, + "id": 23, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.3, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CONCAT('#', i.issue_key, ' ', i.title) AS issue_key, CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' ORDER BY lead_time DESC NULLS LAST LIMIT 10", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Top 10 Issue Lead Time [Last Week's Resolved Bugs in Days]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 58 + }, + "id": 16, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Total Number of Outstanding Bugs", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 30 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 58 + }, + "id": 25, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Average Age of Outstanding Bugs in Days", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 30 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Issue Number" + }, + "properties": [ + { + "id": "custom.width", + "value": 121 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 641 + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.Url}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queue Time in Days" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Url" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Priority" + }, + "properties": [ + { + "id": "custom.width", + "value": 104 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Severity" + }, + "properties": [ + { + "id": "custom.width", + "value": 112 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 58 + }, + "id": 14, + "links": [ + { + "targetBlank": true, + "title": "Bug Count per 1k Lines of Code", + "url": "https://devlake.apache.org/docs/Metrics/BugCountPer1kLinesOfCode" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', priority AS 'Priority', severity AS 'Severity', CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) AND priority = ANY(ARRAY[${priority}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Outstanding Bugs Sort by Queue Time [All History]", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Queue Time in Days", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 18, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.3, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "always", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CONCAT('#', i.issue_key) AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 2 DESC NULLS LAST LIMIT 100", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Top 100 Bug Queue Time in Days [Outstanding Bugs]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 7 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Issue Number" + }, + "properties": [ + { + "id": "custom.width", + "value": 121 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 641 + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.Url}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queue Time in Days" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Url" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Priority" + }, + "properties": [ + { + "id": "custom.width", + "value": 103 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Severity" + }, + "properties": [ + { + "id": "custom.width", + "value": 112 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 31, + "links": [ + { + "targetBlank": true, + "title": "Bug Age", + "url": "https://devlake.apache.org/docs/Metrics/BugAge" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', priority AS 'Priority', severity AS 'Severity', CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Bugs to be Triaged Sort by Queue Time [All History]", + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 76 + }, + "id": 30, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/UserManuals/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard", + "Highlights" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select concat(name, '-', id) as text from boards", + "hide": 0, + "includeAll": true, + "label": "Board", + "multi": false, + "name": "board_id", + "options": [], + "query": "select concat(name, '-', id) as text from boards", + "refresh": 1, + "regex": "/^(?.*)-(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "BUG", + "value": "BUG" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "issue_type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct priority from issues", + "hide": 0, + "includeAll": true, + "label": "Priority", + "multi": true, + "name": "priority", + "options": [], + "query": "select distinct priority from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Weekly Bug Retro (PostgreSQL)", + "uid": "-5EKA5w7k-pg", + "version": 5, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/WeeklyCommunityRetro.json b/grafana/dashboards/postgresql/WeeklyCommunityRetro.json new file mode 100644 index 00000000000..fbaebf9a5eb --- /dev/null +++ b/grafana/dashboards/postgresql/WeeklyCommunityRetro.json @@ -0,0 +1,2447 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 14, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 48, + "links": [], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard can be used to track community growth by OSS maintainers.\n- Data Source Required: GitHub users' organizations are used to filter issues/PRs created by certain users. [Publicize users' org](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/publicizing-or-hiding-organization-membership) so that Apache DevLake can collect users' org.\n- This dashboard **DOES NOT** honor the time filter on the top-right corner.", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 42, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Community Issues", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 5 + }, + "id": 6, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "New Issue Count [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 8, + "y": 5 + }, + "id": 22, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "New Issue Created by Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 14, + "y": 5 + }, + "id": 23, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS community_issue_ratio FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Ratio of New Issue Created by the Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 20, + "y": 5 + }, + "id": 45, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT DISTINCT i.creator_name FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_name IN (SELECT DISTINCT creator_name FROM issues WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT creator_name IS NULL)", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "1st Time Issue Reporter [Previous Week]", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 13 + }, + "id": 24, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT 1 - CAST(COUNT(DISTINCT CASE WHEN comment_id IS NULL THEN issue_id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT issue_id), 0) AS response_rate FROM issue_comment_list", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Community Issues' Response Rate [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 3 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 449 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "url" + }, + "properties": [ + { + "id": "custom.width", + "value": 415 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_id" + }, + "properties": [ + { + "id": "custom.width", + "value": 117 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "status" + }, + "properties": [ + { + "id": "custom.width", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queue_time_in_days" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 95 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 13 + }, + "id": 25, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'queue_time_in_days', url FROM issue_comment_list WHERE comment_id IS NULL", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "List of New Community Issues with no Comments [Previous Week]", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 21 + }, + "id": 26, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "New Community Issues' Average Response Time in Days [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 451 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "creator_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 146 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_created_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 162 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "comment_date" + }, + "properties": [ + { + "id": "custom.width", + "value": 160 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "issue_key" + }, + "properties": [ + { + "id": "custom.width", + "value": 96 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "response_time_in_days" + }, + "properties": [ + { + "id": "custom.width", + "value": 211 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 21 + }, + "id": 28, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH issue_comment_list AS (SELECT SUBSTRING_INDEX(i.url, '/', -1) AS issue_number, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name /* body, */, issue_created_date, comment_date, CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS response_time_in_days, url FROM issue_comment_list WHERE comment_rank = 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "List of New Community Issues' 1st Comment [Previous Week]", + "type": "table" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 44, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Community PRs", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 30 + }, + "id": 31, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of New Pull Requests [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 30 + }, + "id": 34, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of New Pull Requests Created by the Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 30 + }, + "id": 35, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS community_pr_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Ratio of New PR Created by the Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 38 + }, + "id": 36, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Merged Pull Requests Created by the Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 7 + } + ] + }, + "unit": "d" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 38 + }, + "id": 39, + "links": [ + { + "targetBlank": true, + "title": "PR Time To Merge", + "url": "https://devlake.apache.org/docs/Metrics/PRTimeToMerge" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Average Time to Merge of Community Pull Requests in Days [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Contributors: the PR authors who have merged PR(s)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 38 + }, + "id": 46, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT DISTINCT author_name FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND NOT merged_date IS NULL AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_name IN (SELECT DISTINCT author_name FROM pull_requests WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT author_name IS NULL)", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "First time Contributor [Previous Week]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red" + }, + { + "color": "#EAB839", + "value": 0.5 + }, + { + "color": "green", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 46 + }, + "id": 37, + "links": [ + { + "targetBlank": true, + "title": "PR Merge Rate", + "url": "https://devlake.apache.org/docs/Metrics/PRMergeRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS merged_pull_request_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Ratio of Merged Pull Requests Created by the Community [Previous Week]", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 7 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 451 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "queue_time_in_days" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 46 + }, + "id": 51, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT pull_request_key, title, status, author_name, created_date, CAST(((EXTRACT(EPOCH FROM (CURRENT_DATE - created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, url FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "refId": "A", + "select": [ + [ + { + "params": [ + "script_version" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_migration_history", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "List of New Community Issues' 1st Comment [Previous Week]", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Merged PR Count", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 10, + "x": 0, + "y": 54 + }, + "id": 33, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Top 20 Community Contributors By Number of PRs Merged [Previous Week]", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 10, + "y": 54 + }, + "id": 52, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT author_id) AS all_contributor_count FROM pull_requests AS pr WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization = ANY(ARRAY[${org}]::text[])) */", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Top Community Contributors By PRs Created [All History]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 54 + }, + "id": 40, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "first" + ], + "fields": "", + "values": true + }, + "showUnfilled": true, + "text": { + "valueSize": 1 + }, + "valueMode": "color" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Top 20 Community Contributors By Merged PR Numbers [All History]", + "type": "bargauge" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 63 + }, + "id": 50, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "OSS Maintainer Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '-', id) as text from repos", + "hide": 0, + "includeAll": true, + "label": "Repo", + "multi": true, + "name": "repo_id", + "options": [], + "query": "select concat(name, '-', id) as text from repos", + "refresh": 1, + "regex": "/^(?.*)-(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "issue_type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select distinct organization from accounts where organization != ''", + "description": "", + "hide": 0, + "includeAll": true, + "label": "Community Definition - Issues and PRs NOT from Organization(s)", + "multi": true, + "name": "org", + "options": [], + "query": "select distinct organization from accounts where organization != ''", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Weekly Community Retro (PostgreSQL)", + "uid": "VTr6Y_q7z-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/WorkLogs.json b/grafana/dashboards/postgresql/WorkLogs.json new file mode 100644 index 00000000000..125e9797ccd --- /dev/null +++ b/grafana/dashboards/postgresql/WorkLogs.json @@ -0,0 +1,1467 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 39, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 19, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the work logs of a team or a developer\n- Data Sources Required to show all data: \n - One of the Git tools, e.g. [GitHub](https://devlake.apache.org/docs/Configuration/GitHub), [GitLab](https://devlake.apache.org/docs/Configuration/GitLab), [Bitbucket](https://devlake.apache.org/docs/Configuration/BitBucket) or [Azure DevOps](https://devlake.apache.org/docs/Configuration/AzureDevOps)\n - One of the issue tracking tools, e.g. [Jira](https://devlake.apache.org/docs/Configuration/Jira)\n - You also need to complete the [team configuration](https://devlake.apache.org/docs/Configuration/TeamConfiguration) to use this dashboard", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 9, + "title": "Worklogs", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "Comment on PR": { + "color": "super-light-orange", + "index": 5 + }, + "Create an issue": { + "color": "light-blue", + "index": 0 + }, + "Finish a commit": { + "color": "light-purple", + "index": 2 + }, + "Issue resolved": { + "color": "semi-dark-blue", + "index": 1 + }, + "Open a PR": { + "color": "super-light-green", + "index": 3 + }, + "PR gets merged": { + "color": "green", + "index": 4 + }, + "Review PR": { + "color": "orange", + "index": 6 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Activity" + }, + "properties": [ + { + "id": "custom.filterable", + "value": true + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 180 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Date" + }, + "properties": [ + { + "id": "custom.width", + "value": 214 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 228 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Details" + }, + "properties": [ + { + "id": "custom.width", + "value": 991 + } + ] + } + ] + }, + "gridPos": { + "h": 15, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 3, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(resolution_date, '%Y-%m-%d | %W') AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(authored_date, '%Y-%m-%d | %W') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(merged_date, '%Y-%m-%d | %W') AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN \"Date\" ELSE NULL END AS \"Date\", \"Time\", Activity, Details, Name FROM _activities", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Activities by date", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "noValue": "-", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "#ffffff", + "value": null + }, + { + "color": "super-light-green", + "value": 1 + }, + { + "color": "green", + "value": 5 + }, + { + "color": "dark-green", + "value": 20 + } + ] + } + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.width", + "value": 70 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "week_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 208 + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 10, + "x": 0, + "y": 21 + }, + "id": 23, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activity_count_per_day AS (SELECT Date, COUNT(*) AS value FROM _activities GROUP BY 1), last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d, TO_CHAR(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE), 'w%u %Y') AS week_name, TO_CHAR(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE), '%Y%u') AS week_number FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _calendar_months_with_rank AS (SELECT d, CONCAT(week_name, ' (', TO_CHAR(d - INTERVAL 'WEEKDAY DAY', '%m/%d'), ' ~ ', TO_CHAR(d - INTERVAL 'WEEKDAY DAY' + INTERVAL '6 DAY', '%m/%d'), ')') AS week_name, week_number, TO_CHAR(d, '%W') AS weekday, DENSE_RANK() OVER (ORDER BY week_number DESC NULLS LAST) AS week_rank FROM last_few_calendar_months ORDER BY 1 DESC NULLS LAST), _final_dataset AS (SELECT _calendar_months_with_rank.*, CASE WHEN _activity_count_per_day.value IS NULL THEN 0 ELSE _activity_count_per_day.value END AS activity_count FROM _calendar_months_with_rank LEFT JOIN _activity_count_per_day ON _calendar_months_with_rank.d = _activity_count_per_day.Date), WeekSummary AS (SELECT week_name, SUM(CASE WHEN weekday = 'Monday' THEN activity_count END) AS Mon, SUM(CASE WHEN weekday = 'Tuesday' THEN activity_count END) AS Tue, SUM(CASE WHEN weekday = 'Wednesday' THEN activity_count END) AS Wed, SUM(CASE WHEN weekday = 'Thursday' THEN activity_count END) AS Thur, SUM(CASE WHEN weekday = 'Friday' THEN activity_count END) AS Fri, SUM(CASE WHEN weekday = 'Saturday' THEN activity_count END) AS Sat, SUM(CASE WHEN weekday = 'Sunday' THEN activity_count END) AS Sun FROM _final_dataset WHERE week_rank BETWEEN 1 AND 52 GROUP BY week_name) SELECT week_name, Mon, Tue, Wed, Thur, Fri, Sat, Sun FROM WeekSummary ORDER BY CAST(SUBSTRING(week_name FROM STRPOS(week_name, ' ') + 1 FOR 4) AS UBIGINT) DESC NULLS LAST, CAST(SUBSTRING(week_name FROM 2 FOR STRPOS(week_name, ' ') - 2) AS UBIGINT) DESC NULLS LAST", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Activity count in the last year", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "thresholds" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Create an issue" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Issue resolved" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Finish a commit" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Open a PR" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "PR gets merged" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Comment on PR" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Review PR" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 14, + "x": 10, + "y": 21 + }, + "id": 22, + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "normal", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activities_per_day AS (SELECT Date, SUM(CASE WHEN Activity = 'Create an issue' THEN 1 ELSE 0 END) AS 'Create an issue', SUM(CASE WHEN Activity = 'Issue resolved' THEN 1 ELSE 0 END) AS 'Issue resolved', SUM(CASE WHEN Activity = 'Finish a commit' THEN 1 ELSE 0 END) AS 'Finish a commit', SUM(CASE WHEN Activity = 'Open a PR' THEN 1 ELSE 0 END) AS 'Open a PR', SUM(CASE WHEN Activity = 'PR gets merged' THEN 1 ELSE 0 END) AS 'PR gets merged', SUM(CASE WHEN Activity = 'Comment on PR' THEN 1 ELSE 0 END) AS 'Comment on PR', SUM(CASE WHEN Activity = 'Review PR' THEN 1 ELSE 0 END) AS 'Review PR' FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS Date FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY Date DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(_last_7_days.Date, '%d/%m %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.Date = _activities_per_day.Date ORDER BY _last_7_days.Date ASC NULLS FIRST", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Activity distribution in last 7 days", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 10, + "panels": [], + "title": "Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "TODO" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#f2f1eb", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "DONE" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "IN_PROGRESS" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 33 + }, + "id": 4, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _issues AS (SELECT TO_CHAR(created_date, '%d/%m/%Y') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, status, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date)) SELECT status, COUNT(*) FROM _issues GROUP BY 1", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Statuses of assigned issues", + "type": "piechart" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 4, + "y": 33 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name, u.email FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT COUNT(DISTINCT c.sha) FROM commits AS c JOIN _accounts AS a ON c.author_id = a.email WHERE $__timeFilter(authored_date)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Code commits", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 9, + "y": 33 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)) SELECT SUM(additions + deletions) FROM _commits", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Lines of code", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 14, + "y": 33 + }, + "id": 20, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "/.*/", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT pr.id AS pr_id, pr.merged_date, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS details, prc.id AS comment_id, prc.created_date AS comment_date, a.name, prc.type AS comment_type FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id LEFT JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(pr.created_date)) SELECT CONCAT(COUNT(DISTINCT CASE WHEN NOT name IS NULL THEN pr_id END), '/', COUNT(DISTINCT pr_id)) AS text FROM _prs", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "The count of PRs reviewed or commented by the selected users / Total PRs created within this timeframe.", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 41 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "/.*/", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT DISTINCT TO_CHAR(created_date, '%d/%m/%Y') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name, pr.id, pr.merged_date FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date)) SELECT CONCAT(COUNT(CASE WHEN NOT merged_date IS NULL THEN id ELSE NULL END), '/', COUNT(DISTINCT id)) FROM _prs", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "PRs merged / PRs created by the selected users", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 15, + "x": 9, + "y": 41 + }, + "id": 18, + "links": [ + { + "targetBlank": true, + "title": "PR Count", + "url": "https://devlake.apache.org/docs/Metrics/PRCount" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS pr_count FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) AND /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ NOT pr.merged_date IS NULL GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "ae_projects", + "timeColumn": "ae_create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "The monthly count of PRs merged out of the total created by the selected users", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 49 + }, + "id": 11, + "panels": [], + "title": "Batch Size and Latency", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "lines of code" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 0, + "y": 50 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS 'PR Merged Size' FROM _pr_commits_data", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Average PR merged size", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "comments per PR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 5, + "y": 50 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT TO_CHAR(prc.created_date, '%d/%m/%Y') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name, pr.id AS pr_id, prc.id AS prc_id FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(prc.created_date)) SELECT ROUND(CAST(COUNT(DISTINCT prc_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0), 1) FROM _prs", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Average review depth", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "average pr review time from first comment to the last comment", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "m" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 10, + "y": 50 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [], + "fields": "", + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS 'Time to merge' FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Average PR time to merge", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Developer", + "Highlights", + "New" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "None", + "value": "" + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from teams", + "hide": 0, + "includeAll": false, + "label": "Team", + "multi": false, + "name": "team", + "options": [], + "query": "select concat(name, '--', id) from teams", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(users.name, '--', users.id) from users left join team_users on users.id = team_users.user_id where team_users.team_id in ($team)", + "hide": 0, + "includeAll": true, + "label": "User", + "multi": true, + "name": "users", + "options": [], + "query": "select concat(users.name, '--', users.id) from users left join team_users on users.id = team_users.user_id where team_users.team_id in ($team)", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "Work Logs (PostgreSQL)", + "uid": "d449042e-22f0-4357-b8b7-22083f47618d-pg", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Zentao.json b/grafana/dashboards/postgresql/Zentao.json new file mode 100644 index 00000000000..f96beb152cf --- /dev/null +++ b/grafana/dashboards/postgresql/Zentao.json @@ -0,0 +1,1195 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 27, + "links": [ + { + "asDropdown": false, + "icon": "bolt", + "includeVars": false, + "keepTime": true, + "tags": [], + "targetBlank": false, + "title": "Homepage", + "tooltip": "", + "type": "link", + "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "Data Source Specific Dashboard" + ], + "targetBlank": false, + "title": "Metric dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 3, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 128, + "links": [ + { + "targetBlank": true, + "title": "Zentao", + "url": "https://devlake.apache.org/docs/Plugins/zentao" + } + ], + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- Use Cases: This dashboard shows the basic project management metrics from Zentao.\n- Data Source Required: Zentao", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 126, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "1. Issue Throughput", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "1. Total number of issues created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 4 + }, + "id": 114, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Issues [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "id": 116, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Number of Delivered Issue [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 1, + "drawStyle": "bars", + "fillOpacity": 12, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 4 + }, + "id": 120, + "links": [ + { + "targetBlank": true, + "title": "Requirement Count", + "url": "https://devlake.apache.org/docs/Metrics/RequirementCount" + } + ], + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Status Distribution over Month [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 117, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate [Issues Created in Selected Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Issue Delivery Rate = count(Delivered Issues)/count(Issues)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Delivery Rate(%)", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 12, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 121, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Issue Delivery Rate over Time [Issues Created in Selected Time Range]", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 110, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "2. Issue Lead Time", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 14 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 17 + }, + "id": 12, + "links": [ + { + "targetBlank": true, + "title": "Requirement Lead Time", + "url": "https://devlake.apache.org/docs/Metrics/RequirementLeadTime" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "/^value$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time in Days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 21 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 17 + }, + "id": 13, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "80% Issues' Lead Time are less than # days [Issues Resolved in Select Time Range]", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Lead Time(days)", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 16, + "x": 8, + "y": 17 + }, + "id": 17, + "interval": "", + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "options": { + "barRadius": 0, + "barWidth": 0.5, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Mean Issue Lead Time [Issues Resolved in Select Time Range]", + "type": "barchart" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "postgresql", + "description": "1. The cumulative distribution of requirement lead time. \n2. Each point refers to the percent rank of a lead time.", + "fill": 0, + "fillGradient": 4, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 8, + "links": [ + { + "targetBlank": true, + "title": "Requirement Delivery Rate", + "url": "https://devlake.apache.org/docs/Metrics/RequirementDeliveryRate" + } + ], + "nullPointMode": "null", + "options": { + "alertThreshold": false + }, + "percentage": false, + "pluginVersion": "9.5.15", + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "progress" + ], + "type": "column" + } + ] + ], + "table": "ca_analysis", + "timeColumn": "create_time", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "$$hashKey": "object:469", + "colorMode": "ok", + "fill": true, + "line": true, + "op": "lt", + "value": 0.8, + "yaxis": "right" + } + ], + "timeRegions": [], + "title": "Cumulative Distribution of Requirement Lead Time [Issues Resolved in Select Time Range]", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "percentunit", + "label": "Percent Rank (%)", + "logBase": 1, + "max": "1.2", + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 130, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n\nThis dashboard is created based on this [data schema](https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema). Want to add more metrics? Please follow the [guide](https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide).", + "mode": "markdown" + }, + "pluginVersion": "9.5.15", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "Data Source Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "postgresql", + "definition": "select concat(name, '--', id) from boards where id like 'zentao%'", + "hide": 0, + "includeAll": true, + "label": "Choose Board", + "multi": true, + "name": "board_id", + "options": [], + "query": "select concat(name, '--', id) from boards where id like 'zentao%'", + "refresh": 1, + "regex": "/^(?.*)--(?.*)$/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "select distinct type from issues", + "hide": 0, + "includeAll": true, + "label": "Issue Type", + "multi": false, + "name": "type", + "options": [], + "query": "select distinct type from issues", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Zentao (PostgreSQL)", + "uid": "yMb4MKh4k-pg", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_executive.json b/grafana/dashboards/postgresql/qdev_executive.json new file mode 100644 index 00000000000..0aaf4dd3643 --- /dev/null +++ b/grafana/dashboards/postgresql/qdev_executive.json @@ -0,0 +1,768 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Usage (New)", + "tooltip": "Kiro Usage Dashboard - Credits & Messages (new format)", + "type": "link", + "url": "/d/qdev_user_report" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Feature Metrics (Legacy)", + "tooltip": "Kiro Legacy Feature Metrics (old format)", + "type": "link", + "url": "/d/qdev_feature_metrics" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Prompt Logging", + "tooltip": "Kiro AI Activity Insights - Prompt Logging", + "type": "link", + "url": "/d/qdev_logging" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "panels": [], + "title": "KPI Overview (cross-source)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Distinct users with chat activity in the last 7 days (from prompt logging)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'WAU' FROM lake._tool_q_dev_chat_log WHERE timestamp >= NOW() - INTERVAL '7 DAY'", + "refId": "A" + } + ], + "title": "Weekly Active Users (logging)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Average credits spent per accepted line of code (new report + legacy metrics)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(SUM(d.total_accepted), 0), 0), 2) AS 'Credits per Accepted Line' FROM (SELECT user_id, date, SUM(credits_used) AS credits_used FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r JOIN (SELECT user_id, date, (inline_ai_code_lines + chat_ai_code_lines + code_fix_accepted_lines + dev_accepted_lines) AS total_accepted FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)) AS d ON r.user_id = d.user_id AND r.date = d.date", + "refId": "A" + } + ], + "title": "Credits Efficiency (new + legacy)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage of inline suggestions accepted (from legacy feature metrics)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1) AS 'Acceptance %' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Inline Acceptance Rate (legacy)", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Percentage of users who used steering rules (from prompt logging)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100, 0) AS 'Steering %' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Steering Adoption (logging)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 101, + "panels": [], + "title": "User Engagement (logging data: _tool_q_dev_chat_log)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly active user count over time (from prompt logging)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, COUNT(DISTINCT user_id) AS 'Active Users' FROM (SELECT user_id, YEARWEEK(timestamp, 1) AS yw FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY yw ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly Active Users Trend", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "New vs returning users by week (from prompt logging)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, SUM(CASE WHEN yw = first_yw THEN 1 ELSE 0 END) AS 'New Users', SUM(CASE WHEN yw <> first_yw THEN 1 ELSE 0 END) AS 'Returning Users' FROM (SELECT DISTINCT u.user_id, YEARWEEK(u.timestamp, 1) AS yw, f.first_yw FROM lake._tool_q_dev_chat_log AS u JOIN (SELECT user_id, YEARWEEK(MIN(timestamp), 1) AS first_yw FROM lake._tool_q_dev_chat_log GROUP BY user_id) AS f ON u.user_id = f.user_id WHERE $__timeFilter(u.timestamp)) AS weekly GROUP BY yw ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "New vs Returning Users (Weekly)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 102, + "panels": [], + "title": "Credits & Subscription (new format: _tool_q_dev_user_report)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Cumulative credits this month vs projected total (from new user_report)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 8, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER BY date NULLS FIRST) AS 'Cumulative Credits', (SELECT CAST(SUM(credits_used) AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * DAY(CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE) AS DATE)) FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE))) AS 'Projected Monthly' FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE)) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits Pace vs Projected (This Month)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Power tier users with no activity in the last 14 days (from new user_report)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 12, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', MAX(subscription_tier) AS 'Tier', ROUND(SUM(credits_used), 1) AS 'Total Credits Used', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY user_id HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST", + "refId": "A" + } + ], + "title": "Idle Power Users (No Activity in 14 Days)", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 103, + "panels": [], + "title": "Cross-Source: User Productivity (new report + legacy metrics)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Per-user productivity combining credits (new format) with feature metrics (legacy). Only shows users present in both data sources.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 11, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(d.display_name), d.user_id) AS 'User', COALESCE(MAX(r.subscription_tier), '') AS 'Tier', ROUND(SUM(r.credits_used), 1) AS 'Credits Used', SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) AS 'Total Accepted Lines', CASE WHEN SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) > 0 THEN ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines), 0), 2) ELSE NULL END AS 'Credits/Line', CONCAT(ROUND(CAST(SUM(d.inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(d.inline_suggestions_count), 0), 0) * 100, 1), '%') AS 'Accept Rate', SUM(d.code_review_findings_count) AS 'Review Findings', SUM(d.test_generation_event_count) AS 'Test Gen Events', SUM(d.dev_accepted_lines) AS 'Agentic Lines', MIN(d.date) AS 'First Active', MAX(d.date) AS 'Last Active' FROM lake._tool_q_dev_user_data AS d LEFT JOIN (SELECT user_id, date, SUM(credits_used) AS credits_used, MAX(subscription_tier) AS subscription_tier FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r ON d.user_id = r.user_id AND d.date = r.date WHERE $__timeFilter(d.date) GROUP BY d.user_id ORDER BY SUM(r.credits_used) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "User Productivity & Efficiency", + "type": "table" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "executive", + "kiro" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Executive Dashboard (PostgreSQL)", + "uid": "qdev_executive-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_feature_metrics.json b/grafana/dashboards/postgresql/qdev_feature_metrics.json new file mode 100644 index 00000000000..f33bf1b3f06 --- /dev/null +++ b/grafana/dashboards/postgresql/qdev_feature_metrics.json @@ -0,0 +1,948 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "High-level summary of legacy feature-level activity metrics (from by_user_analytic CSV reports)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Users', SUM(inline_suggestions_count) AS 'Inline Suggestions', SUM(inline_acceptance_count) AS 'Inline Accepted', SUM(chat_messages_sent) AS 'Chat Messages', SUM(chat_ai_code_lines) AS 'Chat AI Lines', SUM(code_review_findings_count) AS 'Review Findings', SUM(test_generation_event_count) AS 'Test Gen Events', SUM(dev_accepted_lines) AS 'Agentic Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Legacy Feature Metrics Overview", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 20, + "panels": [], + "title": "Inline Suggestions", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Daily inline suggestion and acceptance counts", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(inline_suggestions_count) AS 'Suggestions', SUM(inline_acceptance_count) AS 'Accepted', SUM(inline_ai_code_lines) AS 'AI Code Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Inline Suggestions & Acceptance", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Acceptance rates for inline suggestions, code fix, and inline chat over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Inline Suggestions', CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Code Fix', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Inline Chat' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Acceptance Rate Trends", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 21, + "panels": [], + "title": "Chat & Agentic (Dev)", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Daily chat messages sent and AI-generated code lines from chat", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS 'Messages Sent', SUM(chat_messages_interacted) AS 'Messages Interacted', SUM(chat_ai_code_lines) AS 'AI Code Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Chat Activity", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Agentic (Dev) code generation and acceptance metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS 'Generation Events', SUM(dev_generated_lines) AS 'Generated Lines', SUM(dev_accepted_lines) AS 'Accepted Lines', SUM(dev_acceptance_event_count) AS 'Acceptance Events' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Agentic (Dev) Activity", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 22, + "panels": [], + "title": "Code Review, Test Gen & Transformations", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Code review findings and test generation metrics over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(code_review_findings_count) AS 'Review Findings', SUM(code_review_succeeded_event_count) AS 'Reviews Succeeded', SUM(code_review_failed_event_count) AS 'Reviews Failed' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Code Review Activity", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Test generation events and acceptance over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 25 + }, + "id": 7, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS 'Test Gen Events', SUM(test_generation_generated_tests) AS 'Tests Generated', SUM(test_generation_accepted_tests) AS 'Tests Accepted', SUM(test_generation_generated_lines) AS 'Lines Generated', SUM(test_generation_accepted_lines) AS 'Lines Accepted' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Test Generation Activity", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Doc generation and code transformation events", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 33 + }, + "id": 8, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS 'Doc Gen Events', SUM(doc_generation_accepted_line_additions) AS 'Doc Lines Accepted', SUM(transformation_event_count) AS 'Transformation Events', SUM(transformation_lines_generated) AS 'Transform Lines Generated' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Doc Generation & Transformations", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Number of users who used each feature in the selected period", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.8, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 33 + }, + "id": 9, + "options": { + "barRadius": 0.1, + "barWidth": 0.8, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0 + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT 'Chat' AS Feature, COUNT(DISTINCT CASE WHEN chat_messages_sent > 0 THEN user_id END) AS Users FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Inline Suggestions', COUNT(DISTINCT CASE WHEN inline_suggestions_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Fix', COUNT(DISTINCT CASE WHEN code_fix_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Review', COUNT(DISTINCT CASE WHEN code_review_succeeded_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Doc Generation', COUNT(DISTINCT CASE WHEN doc_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Test Generation', COUNT(DISTINCT CASE WHEN test_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Dev (Agentic)', COUNT(DISTINCT CASE WHEN dev_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Transformation', COUNT(DISTINCT CASE WHEN transformation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Feature Adoption (Users per Feature)", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 23, + "panels": [], + "title": "Per-User Detail", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Per-user breakdown of legacy feature-level metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 10, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', SUM(inline_suggestions_count) AS 'Suggestions', SUM(inline_acceptance_count) AS 'Accepted', CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1), '%') AS 'Accept %', SUM(chat_messages_sent) AS 'Chat Msgs', SUM(chat_ai_code_lines) AS 'Chat Lines', SUM(dev_accepted_lines) AS 'Agentic Lines', SUM(code_review_findings_count) AS 'Review Findings', SUM(test_generation_accepted_tests) AS 'Tests Accepted', SUM(doc_generation_event_count) AS 'Doc Gen', SUM(transformation_event_count) AS 'Transforms', MIN(date) AS 'First Active', MAX(date) AS 'Last Active' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_suggestions_count) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Per-User Feature Metrics", + "type": "table" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "legacy", + "kiro" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Legacy Feature Metrics (PostgreSQL)", + "uid": "qdev_feature_metrics-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_logging.json b/grafana/dashboards/postgresql/qdev_logging.json new file mode 100644 index 00000000000..5630c69cb8c --- /dev/null +++ b/grafana/dashboards/postgresql/qdev_logging.json @@ -0,0 +1,1118 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Overview of logging event metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT (SELECT COUNT(*) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Chat Events', (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Chat Users', (SELECT COUNT(DISTINCT conversation_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) AND conversation_id <> '') AS 'Conversations', (SELECT COUNT(*) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS 'Completion Events', (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS 'Completion Users', (SELECT SUM(code_reference_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Code References', (SELECT SUM(web_link_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Web Links Cited'", + "refId": "A" + } + ], + "title": "Logging Overview", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Hourly distribution of AI usage activity (chat + completions)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hour of Day", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.8, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT LPAD(CAST(hour_of_day AS TEXT), 2, '0') AS 'Hour', SUM(chat_count) AS 'Chat Events', SUM(completion_count) AS 'Completion Events' FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, COUNT(*) AS chat_count, 0 AS completion_count FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 0 AS chat_count, COUNT(*) AS completion_count FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS combined GROUP BY hour_of_day ORDER BY hour_of_day NULLS FIRST", + "refId": "A" + } + ], + "title": "Active Hours Distribution", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "Distribution of chat trigger types: MANUAL (chat window) vs INLINE_CHAT", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 14 + }, + "id": 11, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS 'Trigger Type', COUNT(*) AS 'Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY chat_trigger_type ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Chat Trigger Type Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Distribution of model usage across chat events", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 14 + }, + "id": 3, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Requests$/", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS 'Model', COUNT(*) AS 'Requests' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Model Usage Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Top file extensions used with inline completions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 14 + }, + "id": 4, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS 'File Type', COUNT(*) AS 'Completions' FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "refId": "A" + } + ], + "title": "File Type Usage (Completions)", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Average number of chat events per conversation", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CAST(COUNT(*) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END), 0), 0) AS 'Avg Turns per Conversation', COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS 'Unique Conversations', COUNT(*) AS 'Total Chat Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "refId": "A" + } + ], + "title": "Conversation Depth Analysis", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily chat and completion events over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT time, SUM(chat) AS 'Chat Events', SUM(completions) AS 'Completion Events' FROM (SELECT CAST(timestamp AS DATE) AS time, COUNT(*) AS chat, 0 AS completions FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) UNION ALL SELECT CAST(timestamp AS DATE) AS time, 0 AS chat, COUNT(*) AS completions FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE)) AS combined GROUP BY time ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Event Trends", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Per-user logging activity summary", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(u.display_name, u.user_id) AS 'User', u.user_id AS 'User ID', u.chat_events AS 'Chat Events', u.conversations AS 'Conversations', ROUND(CAST(u.chat_events AS NUMERIC) / NULLIF(NULLIF(u.conversations, 0), 0), 1) AS 'Avg Turns', COALESCE(c.completion_events, 0) AS 'Completion Events', COALESCE(c.files_count, 0) AS 'Distinct Files', ROUND(u.avg_prompt_len) AS 'Avg Prompt Len', ROUND(u.avg_response_len) AS 'Avg Response Len', u.steering_count AS 'Steering Uses', u.spec_count AS 'Spec Mode Uses', u.code_ref_count AS 'Code Refs', u.web_link_count AS 'Web Links', u.models_used AS 'Models Used', u.first_seen AS 'First Seen', GREATEST(u.last_seen, COALESCE(c.last_seen, u.last_seen)) AS 'Last Seen' FROM (SELECT user_id, MAX(display_name) AS display_name, COUNT(*) AS chat_events, COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS conversations, AVG(prompt_length) AS avg_prompt_len, AVG(response_length) AS avg_response_len, STRING_AGG(DISTINCT CASE WHEN model_id <> '' AND NOT model_id IS NULL THEN model_id END, ', ' ORDER BY model_id NULLS FIRST) AS models_used, SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS steering_count, SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS spec_count, SUM(code_reference_count) AS code_ref_count, SUM(web_link_count) AS web_link_count, MIN(timestamp) AS first_seen, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS u LEFT JOIN (SELECT user_id, COUNT(*) AS completion_events, COUNT(DISTINCT file_name) AS files_count, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS c ON u.user_id = c.user_id ORDER BY u.user_id NULLS FIRST", + "refId": "A" + } + ], + "title": "Per-User Activity", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Distribution of Kiro feature adoption: Steering, Spec Mode, and Plain Chat", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 48 + }, + "id": 8, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Using Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Using Spec Mode', SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS 'Plain Chat' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Kiro Feature Adoption", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Top file extensions active during chat events", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 48 + }, + "id": 9, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file active)' ELSE active_file_extension END AS 'File Type', COUNT(*) AS 'Chat Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "refId": "A" + } + ], + "title": "Active File Types in Chat", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "How often Kiro responses include code references and web links", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 48 + }, + "id": 12, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(CASE WHEN code_reference_count > 0 THEN 1 ELSE 0 END) AS 'With Code References', SUM(CASE WHEN web_link_count > 0 THEN 1 ELSE 0 END) AS 'With Web Links', SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS 'With Followup Prompts', SUM(CASE WHEN code_reference_count = 0 AND web_link_count = 0 AND has_followup_prompts = FALSE THEN 1 ELSE 0 END) AS 'Plain Response' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "refId": "A" + } + ], + "title": "Response Enrichment Breakdown", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Average and maximum prompt/response lengths over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 56 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, AVG(prompt_length) AS 'Avg Prompt Length', AVG(response_length) AS 'Avg Response Length', MAX(prompt_length) AS 'Max Prompt Length', MAX(response_length) AS 'Max Response Length' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "refId": "A" + } + ], + "title": "Prompt & Response Length Trends", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Average code context size provided to inline completions over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Characters", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 64 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, ROUND(AVG(left_context_length)) AS 'Avg Left Context', ROUND(AVG(right_context_length)) AS 'Avg Right Context', ROUND(AVG(left_context_length + right_context_length)) AS 'Avg Total Context' FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "refId": "A" + } + ], + "title": "Completion Context Size Trends", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily trend of code references and web links in chat responses", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, SUM(code_reference_count) AS 'Code References', SUM(web_link_count) AS 'Web Links', SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS 'Followup Prompts' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "refId": "A" + } + ], + "title": "Response Enrichment Trends", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "logging", + "kiro" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro AI Activity Insights (PostgreSQL)", + "uid": "qdev_logging-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_user_data.json b/grafana/dashboards/postgresql/qdev_user_data.json new file mode 100644 index 00000000000..5afd9d8f184 --- /dev/null +++ b/grafana/dashboards/postgresql/qdev_user_data.json @@ -0,0 +1,1182 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 100, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Overview of key user metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Users', SUM(chat_ai_code_lines) AS 'Accepted Lines (Chat)', SUM(inline_ai_code_lines) AS 'Accepted Lines (Inline Suggestion)', CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Acceptance Rate (Inline Suggestion)', SUM(code_review_findings_count) AS 'Findings (Code Review)', SUM(code_fix_accepted_lines) AS 'Accepted Lines (Code Fix)', CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Acceptance Rate (Code Fix)', SUM(transformation_lines_ingested) AS 'Ingested Lines (Java Transform)', SUM(transformation_lines_generated) AS 'Generated Lines (Java Transform)', SUM(inline_chat_accepted_line_additions) AS 'Accepted Lines (Inline Chat)', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Acceptance Rate (Inline Chat)' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Overall Usage Statistics", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Daily AI code line changes across all users", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(chat_ai_code_lines) AS 'Chat Accepted Lines', SUM(code_fix_accepted_lines) AS 'Code Fix Accepted Lines', SUM(code_fix_generated_lines) AS 'Code Fix Generated Lines', SUM(transformation_lines_ingested) AS 'Java Transform Ingested Lines', SUM(transformation_lines_generated) AS 'Java Transform Generated Lines', SUM(inline_ai_code_lines) AS 'Inline Suggestion Accepted Lines', SUM(inline_chat_accepted_line_additions) AS 'Inline Chat Accepted Line Additions', SUM(inline_chat_accepted_line_deletions) AS 'Inline Chat Accepted Line Deletions', SUM(inline_chat_dismissed_line_additions) AS 'Inline Chat Dismissed Line Additions', SUM(inline_chat_dismissed_line_deletions) AS 'Inline Chat Dismissed Line Deletions', SUM(inline_chat_rejected_line_additions) AS 'Inline Chat Rejected Line Additions', SUM(inline_chat_rejected_line_deletions) AS 'Inline Chat Rejected Line Deletions' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Daily AI Code Line Changes", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily AI interaction trends across all users", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 9, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS 'Chat Messages Sent', SUM(code_fix_acceptance_event_count) AS 'Code Fix Accepted Event Count', SUM(code_fix_generation_event_count) AS 'Code Fix Generated Event Count', SUM(transformation_event_count) AS 'Java Transform Event Count', SUM(inline_acceptance_count) AS 'Inline Suggestion Accepted Suggestions', SUM(inline_suggestions_count) AS 'Inline Suggestion Count', SUM(inline_chat_total_event_count) AS 'Inline Chat Total Suggestions', SUM(inline_chat_acceptance_event_count) AS 'Inline Chat Accepted Suggestions', SUM(inline_chat_dismissal_event_count) AS 'Inline Chat Dismissed Suggestions', SUM(inline_chat_rejection_event_count) AS 'Inline Chat Rejected Suggestions' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Daily AI Interactions", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Code review metrics over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(code_fix_acceptance_event_count) AS 'Code Fix Accepted Event Count', SUM(code_fix_generation_event_count) AS 'Code Fix Generated Event Count', SUM(code_review_findings_count) AS 'Total Findings' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Code Review Metrics", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily acceptance rate of AI suggestions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 5, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Code Fix', CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Inline Suggestions', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Inline Chat' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Daily AI Suggestion Acceptance Rate", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "User AI interaction metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Acceptance Rate" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Inline Chat Accepted Events" + }, + "properties": [ + { + "id": "custom.width", + "value": 239 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Acceptance Rate (Inline Suggestion)" + }, + "properties": [ + { + "id": "custom.width", + "value": 172 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 6, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', SUM(chat_ai_code_lines) AS 'Accepted Lines (Chat)', SUM(transformation_lines_ingested) AS 'Lines Ingested (Java Transform)', SUM(transformation_lines_generated) AS 'Lines Generated (Java Transform)', SUM(transformation_event_count) AS 'Event Count (Java Transform)', SUM(code_review_findings_count) AS 'Findings (Code Review)', SUM(code_fix_accepted_lines) AS 'Accepted Lines (Code Fix)', SUM(code_fix_generated_lines) AS 'Generated Lines (Code Fix)', SUM(code_fix_acceptance_event_count) AS 'Accepted Count (Code Fix)', SUM(code_fix_generation_event_count) AS 'Generated Count (Code Fix)', CONCAT(ROUND(CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Code Fix)', SUM(inline_ai_code_lines) AS 'Accepted Lines (Inline Suggestion)', SUM(inline_acceptance_count) AS 'Accepted Count (Inline Suggestion)', SUM(inline_suggestions_count) AS 'Total Count (Inline Suggestion)', CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Inline Suggestion)', SUM(inline_chat_accepted_line_additions) AS 'Accepted Line Additions (Inline Chat)', SUM(inline_chat_accepted_line_deletions) AS 'Accepted Line Deletions (Inline Chat)', SUM(inline_chat_acceptance_event_count) AS 'Accepted Events (Inline Chat)', SUM(inline_chat_total_event_count) AS 'Total Events (Inline Chat)', CONCAT(ROUND(CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Inline Chat)', SUM(doc_generation_event_count) AS 'Doc Gen Events', SUM(test_generation_event_count) AS 'Test Gen Events', SUM(dev_accepted_lines) AS 'Dev Accepted Lines', MIN(date) AS 'First Activity', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_ai_code_lines) DESC NULLS LAST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "User Interactions", + "type": "table" + }, + { + "datasource": "postgresql", + "description": "Daily doc generation events and accepted/rejected lines", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 40 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS 'Doc Generation Events', SUM(doc_generation_accepted_line_additions) AS 'Accepted Line Additions', SUM(doc_generation_accepted_line_updates) AS 'Accepted Line Updates', SUM(doc_generation_rejected_line_additions) AS 'Rejected Line Additions', SUM(doc_generation_rejected_line_updates) AS 'Rejected Line Updates' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Doc Generation Metrics", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily test generation events and lines", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS 'Test Generation Events', SUM(test_generation_accepted_tests) AS 'Accepted Tests', SUM(test_generation_generated_tests) AS 'Generated Tests', SUM(test_generation_accepted_lines) AS 'Accepted Lines', SUM(test_generation_generated_lines) AS 'Generated Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Test Generation Metrics", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily agentic dev events and lines", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 48 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS 'Dev Generation Events', SUM(dev_acceptance_event_count) AS 'Dev Acceptance Events', SUM(dev_generated_lines) AS 'Dev Generated Lines', SUM(dev_accepted_lines) AS 'Dev Accepted Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Dev (Agentic) Metrics", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "user_data" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Code Metrics Dashboard (PostgreSQL)", + "uid": "qdev_user_data-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_user_report.json b/grafana/dashboards/postgresql/qdev_user_report.json new file mode 100644 index 00000000000..692ab01f96c --- /dev/null +++ b/grafana/dashboards/postgresql/qdev_user_report.json @@ -0,0 +1,464 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 101, + "links": [], + "panels": [ + { + "datasource": "postgresql", + "description": "Overview of credits and usage metrics", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT SUM(credits_used) AS 'Total Credits Used', COUNT(DISTINCT user_id) AS 'Active Users', SUM(total_messages) AS 'Total Messages', SUM(chat_conversations) AS 'Total Conversations' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Overview Stats", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "Daily credits consumed broken down by subscription tier", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, subscription_tier AS metric, SUM(credits_used) AS value FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date, subscription_tier ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Credits Consumed by Subscription Tier", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Daily messages and conversations broken down by client type", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT date AS time, SUM(CASE WHEN client_type = 'KIRO_IDE' THEN total_messages ELSE 0 END) AS 'Messages (IDE)', SUM(CASE WHEN client_type = 'KIRO_CLI' THEN total_messages ELSE 0 END) AS 'Messages (CLI)', SUM(CASE WHEN client_type = 'PLUGIN' THEN total_messages ELSE 0 END) AS 'Messages (Plugin)', SUM(chat_conversations) AS 'Conversations' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "refId": "A" + } + ], + "title": "Daily Messages & Conversations", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Distribution of users across subscription tiers", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 4, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT subscription_tier AS 'Tier', COUNT(DISTINCT user_id) AS 'Users' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY subscription_tier ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Subscription Tier Distribution", + "type": "piechart" + }, + { + "datasource": "postgresql", + "description": "Per-user credits, messages, and subscription details", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Credits Used" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Overage" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "Yes": { + "color": "orange", + "index": 0 + }, + "No": { + "color": "green", + "index": 1 + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 5, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.6.2", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', subscription_tier AS 'Tier', client_type AS 'Client', SUM(credits_used) AS 'Credits Used', SUM(total_messages) AS 'Messages', SUM(chat_conversations) AS 'Conversations', SUM(overage_credits_used) AS 'Overage Credits', CASE WHEN MAX(CAST(overage_enabled AS UBIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS 'Overage', MIN(date) AS 'First Activity', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, subscription_tier, client_type ORDER BY user_id DESC NULLS LAST", + "refId": "A" + } + ], + "title": "Per-User Credits & Activity", + "type": "table" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "user_report", + "kiro" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "Kiro Usage Dashboard (PostgreSQL)", + "uid": "qdev_user_report-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/scripts/convert-mysql-to-postgresql.py b/grafana/scripts/convert-mysql-to-postgresql.py new file mode 100755 index 00000000000..4185d12e267 --- /dev/null +++ b/grafana/scripts/convert-mysql-to-postgresql.py @@ -0,0 +1,667 @@ +#!/usr/bin/env python3 +""" +Convert Grafana dashboards from MySQL to PostgreSQL datasource. +Uses sqlglot for AST-based SQL parsing and transformation. +""" + +import json +import sys +import re +from pathlib import Path +from typing import Any, Dict + +try: + import sqlglot +except ImportError: + print("ERROR: sqlglot not installed. Run: pip install -r requirements.txt") + sys.exit(1) + + +def convert_sql_mysql_to_postgres(sql: str) -> str: + """ + Convert MySQL SQL to PostgreSQL using sqlglot AST parsing. + Falls back to regex for patterns sqlglot doesn't handle. + """ + if not sql or not sql.strip(): + return sql + + try: + # Protect string literals from function conversions + sql, string_literals = protect_string_literals(sql) + + # Protect Grafana template variables from sqlglot conversion + # sqlglot treats ${var} as MySQL user variables → STRUCT(var) + sql, placeholders = protect_grafana_variables(sql) + + # Try sqlglot transpilation first + converted = sqlglot.transpile(sql, read='mysql', write='postgres')[0] + + # Post-process patterns sqlglot might miss + converted = post_process_sql(converted) + + # Restore Grafana macros to lowercase (sqlglot uppercases them) + converted = restore_grafana_macros(converted) + + # Restore Grafana template variables + converted = restore_grafana_variables(converted, placeholders) + + # Restore string literals + converted = restore_string_literals(converted, string_literals) + + return converted + except Exception as e: + # Fallback to regex-based conversion for complex cases + print(f" [WARN] sqlglot failed, using regex fallback: {str(e)[:100]}") + return regex_fallback_conversion(sql) + + +def protect_string_literals(sql_text: str) -> tuple[str, dict]: + """ + Replace all string literals with placeholders to protect them from conversion. + Returns modified SQL and mapping of placeholders to original strings. + """ + literals = {} + counter = 0 + result = [] + i = 0 + + while i < len(sql_text): + if sql_text[i] in ("'", '"'): + quote_char = sql_text[i] + literal_parts = [sql_text[i]] + i += 1 + while i < len(sql_text): + literal_parts.append(sql_text[i]) + if sql_text[i] == quote_char: + # Check for escaped quote ('' or "") + if i + 1 < len(sql_text) and sql_text[i + 1] == quote_char: + literal_parts.append(sql_text[i + 1]) + i += 2 + else: + i += 1 + break + else: + i += 1 + + literal = ''.join(literal_parts) + placeholder = f'STRING_LITERAL_{counter}_PLACEHOLDER' + literals[placeholder] = literal + result.append(placeholder) + counter += 1 + else: + result.append(sql_text[i]) + i += 1 + + return ''.join(result), literals + + +def restore_string_literals(sql_text: str, literals: dict) -> str: + """Restore string literals from placeholders.""" + for placeholder, literal in literals.items(): + sql_text = sql_text.replace(placeholder, literal) + return sql_text + + +def replace_function_with_balanced_parens(sql_text: str, func_pattern: str, replacement_func) -> str: + """ + Generic function replacer that handles nested parentheses via depth counting. + + Args: + sql_text: SQL string to process (string literals already protected) + func_pattern: Regex pattern for function name (e.g., r'\\bHOUR\\s*\\(') + replacement_func: Function taking matched argument and returning replacement string + + Returns: + Modified SQL with function replaced + """ + result = [] + i = 0 + pattern = re.compile(func_pattern, re.IGNORECASE) + while i < len(sql_text): + match = pattern.search(sql_text, i) + if not match: + result.append(sql_text[i:]) + break + result.append(sql_text[i:match.start()]) + start = match.end() + depth = 1 + j = start + while j < len(sql_text) and depth > 0: + if sql_text[j] == '(': + depth += 1 + elif sql_text[j] == ')': + depth -= 1 + j += 1 + if depth == 0: + arg = sql_text[start:j-1] + result.append(replacement_func(arg)) + i = j + else: + result.append(match.group(0)) + i = start + return ''.join(result) + + +def post_process_sql(sql: str) -> str: + """ + Post-process SQL after sqlglot conversion for Grafana-specific patterns. + """ + # Remove backticks (PostgreSQL uses double quotes or no quotes) + sql = sql.replace('`', '"') + + # CURDATE() -> CURRENT_DATE + sql = re.sub(r'\bCURDATE\s*\(\s*\)', 'CURRENT_DATE', sql, flags=re.IGNORECASE) + + # FORMAT(value, decimals) -> ROUND(value, decimals) + # MySQL FORMAT() formats number with commas and decimal places + # PostgreSQL ROUND() works - auto-casts to text in CONCAT context + # Don't add ::text here because FORMAT has nested functions with commas + # and [^,]+ regex can't handle them properly + sql = re.sub( + r'\bFORMAT\s*\([^)]*\)', + lambda m: m.group(0).replace('FORMAT', 'ROUND', 1), + sql, + flags=re.IGNORECASE + ) + + # Cast Grafana time macros to timestamp when used with INTERVAL + # $__timeFrom() + INTERVAL → $__timeFrom()::timestamp + INTERVAL + # $__timeTo() + INTERVAL → $__timeTo()::timestamp + INTERVAL + # Also handle when wrapped in parentheses: ($__timeTo() - INTERVAL + sql = re.sub( + r'(\$__timeFrom\(\)|\$__timeTo\(\))\s*([+\-])\s*INTERVAL', + r'\1::timestamp \2 INTERVAL', + sql, + flags=re.IGNORECASE + ) + # Handle parentheses-wrapped time macros: ($__timeFrom() or ($__timeTo() + sql = re.sub( + r'\((\$__timeFrom\(\)|\$__timeTo\(\))\s*([+\-])\s*INTERVAL', + r'(\1::timestamp \2 INTERVAL', + sql, + flags=re.IGNORECASE + ) + + # Remove CHARACTER SET from CAST (MySQL-specific) + # CAST(col AS CHAR CHARACTER SET utf8mb4) → CAST(col AS TEXT) + sql = re.sub( + r'\bCAST\s*\(([^)]+)\s+AS\s+CHAR\s+CHARACTER\s+SET\s+\w+\)', + r'CAST(\1 AS TEXT)', + sql, + flags=re.IGNORECASE + ) + + # CAST AS CHAR → CAST AS TEXT (PostgreSQL doesn't have bare CHAR for casting) + # Use global replace to handle nested CAST expressions + sql = re.sub( + r'\s+AS\s+CHAR\)', + ' AS TEXT)', + sql, + flags=re.IGNORECASE + ) + + # HOUR() -> EXTRACT(HOUR FROM ...) + sql = replace_function_with_balanced_parens( + sql, + r'\bHOUR\s*\(', + lambda arg: f'EXTRACT(HOUR FROM {arg})' + ) + + # Boolean comparisons: column = 1 → column = TRUE, column = 0 → column = FALSE + # PostgreSQL schema has BOOLEAN type - does NOT accept integer comparison + # ERROR: "operator does not exist: boolean = integer" + # Must convert ALL boolean column comparisons: WHERE/AND/WHEN + # Safe: CASE WHEN bool_col = TRUE THEN 1 - condition is boolean, result is integer + # THEN/ELSE values determine CASE return type, not WHEN condition type + sql = re.sub( + r'(WHERE|AND|WHEN)\s+([\w.]*(?:has_|is_)[\w.]+|[\w.]+_flag)\s*=\s*1\b', + r'\1 \2 = TRUE', + sql, + flags=re.IGNORECASE + ) + sql = re.sub( + r'(WHERE|AND|WHEN)\s+([\w.]*(?:has_|is_)[\w.]+|[\w.]+_flag)\s*=\s*0\b', + r'\1 \2 = FALSE', + sql, + flags=re.IGNORECASE + ) + + # DAY_OF_WEEK -> EXTRACT(ISODOW FROM ...) + # MySQL DAY_OF_WEEK() returns 1=Sunday, 2=Monday, 3=Tuesday...7=Saturday + # PostgreSQL ISODOW returns 1=Monday, 2=Tuesday...7=Sunday + sql = replace_function_with_balanced_parens( + sql, + r'\bDAY_OF_WEEK\s*\(', + lambda arg: f'EXTRACT(ISODOW FROM {arg})' + ) + + # Fix weekday CASE mapping after DAY_OF_WEEK → ISODOW conversion + # Old MySQL mapping: '2'→Monday, '3'→Tuesday...'1'→Sunday + # New ISODOW mapping: '1'→Monday, '2'→Tuesday...'7'→Sunday + weekday_replacements = { + "WHEN '2' THEN '1.Monday'": "WHEN '1' THEN '1.Monday'", + "WHEN '3' THEN '2.Tuesday'": "WHEN '2' THEN '2.Tuesday'", + "WHEN '4' THEN '3.Wednesday'": "WHEN '3' THEN '3.Wednesday'", + "WHEN '5' THEN '4.Thursday'": "WHEN '4' THEN '4.Thursday'", + "WHEN '6' THEN '5.Friday'": "WHEN '5' THEN '5.Friday'", + "WHEN '7' THEN '6.Saturday'": "WHEN '6' THEN '6.Saturday'", + "WHEN '1' THEN '7.Sunday'": "WHEN '7' THEN '7.Sunday'" + } + for old, new in weekday_replacements.items(): + sql = sql.replace(old, new) + + # DOUBLE PRECISION -> NUMERIC + # PostgreSQL ROUND() doesn't accept DOUBLE PRECISION, needs NUMERIC + # Simple global replacement since NUMERIC is compatible everywhere DOUBLE PRECISION is used + sql = re.sub( + r'\bDOUBLE\s+PRECISION\b', + 'NUMERIC', + sql, + flags=re.IGNORECASE + ) + + # FIND_IN_SET -> ANY(string_to_array()) + # MySQL: FIND_IN_SET(value, csv_string) > 0 + # PostgreSQL: value = ANY(string_to_array(csv_string, ',')) + # Handle nested functions by matching balanced parentheses + def replace_find_in_set(sql): + pattern = r'FIND_IN_SET\s*\(' + result = [] + i = 0 + while i < len(sql): + match = re.match(pattern, sql[i:], re.IGNORECASE) + if match: + start = i + match.end() + # Find matching closing paren and split args + depth = 1 + arg1_end = None + for j in range(start, len(sql)): + if sql[j] == '(': + depth += 1 + elif sql[j] == ')': + depth -= 1 + if depth == 0: + # Found closing paren, now find the comma separator + for k in range(start, j): + if sql[k] == ',' and depth == 1: + # Count depth properly + temp_depth = 0 + valid_comma = True + for m in range(start, k): + if sql[m] == '(': + temp_depth += 1 + elif sql[m] == ')': + temp_depth -= 1 + if temp_depth == 0: + arg1_end = k + break + if arg1_end: + arg1 = sql[start:arg1_end].strip() + arg2 = sql[arg1_end+1:j].strip() + # Check if followed by > 0 + rest_start = j + 1 + gt_match = re.match(r'\s*>\s*0', sql[rest_start:]) + if gt_match: + result.append(f"{arg1} = ANY(string_to_array({arg2}, ','))") + i = rest_start + gt_match.end() + continue + break + elif sql[j] == ',' and depth == 1: + arg1_end = j + result.append(sql[i]) + i += 1 + else: + result.append(sql[i]) + i += 1 + return ''.join(result) + + # Simpler approach: iteratively replace innermost FIND_IN_SET first + prev_sql = None + while prev_sql != sql: + prev_sql = sql + sql = re.sub( + r'FIND_IN_SET\s*\(([^(),]+),\s*([^()]+)\)\s*>\s*0', + r"\1 = ANY(string_to_array(\2, ','))", + sql, + flags=re.IGNORECASE + ) + + # Fix malformed INTERVAL (missing number) + # INTERVAL DAY -> INTERVAL '1 day' + sql = re.sub( + r'\bINTERVAL\s+DAY\b', + "INTERVAL '1 day'", + sql, + flags=re.IGNORECASE + ) + + # ArgoCD images column is JSON - cast to text for GROUP BY/comparisons + # Pattern: ri.images (without ::text already) + # PostgreSQL JSONB requires explicit cast for equality/GROUP BY + sql = re.sub( + r'\bri\.images\b(?!\s*::)', + 'ri.images::text', + sql, + flags=re.IGNORECASE + ) + + # IFNULL -> COALESCE (if sqlglot missed it) + sql = re.sub(r'\bIFNULL\s*\(', 'COALESCE(', sql, flags=re.IGNORECASE) + + # UNIX_TIMESTAMP -> EXTRACT(EPOCH FROM ...) + sql = replace_function_with_balanced_parens( + sql, + r'\bUNIX_TIMESTAMP\s*\(', + lambda arg: f'EXTRACT(EPOCH FROM {arg})' + ) + + # GROUP_CONCAT -> STRING_AGG + sql = replace_function_with_balanced_parens( + sql, + r'\bGROUP_CONCAT\s*\(', + lambda arg: f"STRING_AGG({arg}, ',')" + ) + + # TIMESTAMPDIFF - handle sqlglot format: TIMESTAMPDIFF(end, start, unit) + # PostgreSQL needs: EXTRACT(EPOCH FROM (end - start)) / divisor + # Use generic function to handle nested parens in arguments + def replace_timestampdiff(args): + # Parse comma-separated args manually (can't use split because of nested parens) + parts = [] + depth = 0 + current = [] + for char in args: + if char == '(': + depth += 1 + current.append(char) + elif char == ')': + depth -= 1 + current.append(char) + elif char == ',' and depth == 0: + parts.append(''.join(current).strip()) + current = [] + else: + current.append(char) + if current: + parts.append(''.join(current).strip()) + + if len(parts) >= 3: + end, start, unit = parts[0], parts[1], parts[2].upper() + # Map unit to divisor + divisors = {'DAY': '86400', 'HOUR': '3600', 'MINUTE': '60', 'SECOND': '1'} + divisor = divisors.get(unit, '1') + if divisor == '1': + return f'EXTRACT(EPOCH FROM ({end} - {start}))' + else: + return f'(EXTRACT(EPOCH FROM ({end} - {start}))/{divisor})' + return f'TIMESTAMPDIFF({args})' # Fallback if parse fails + + sql = replace_function_with_balanced_parens( + sql, + r'\bTIMESTAMPDIFF\s*\(', + replace_timestampdiff + ) + + # DIV(TO_CHAR(date, 'YYYY-MM'), N) -> calculate month-based division + # Used for bucketing dates into N-month periods (e.g., half-years with N=6) + sql = re.sub( + r'DIV\s*\(\s*TO_CHAR\s*\(\s*([^,]+?)\s*,\s*["\']YYYY-MM["\']\s*\)\s*,\s*(\d+)\s*\)', + r'((EXTRACT(YEAR FROM \1) * 12 + EXTRACT(MONTH FROM \1)) / \2)', + sql, + flags=re.IGNORECASE + ) + + return sql + + +def protect_grafana_variables(sql: str) -> tuple: + """ + Replace Grafana template variables with safe placeholders before sqlglot. + sqlglot treats ${var} as MySQL user variables and converts to STRUCT(var). + Returns: (modified_sql, dict of placeholders) + """ + placeholders = {} + counter = 0 + + # Match Grafana variables: ${variable} or ${variable:format} + pattern = r'\$\{([^}]+)\}' + + def replacer(match): + nonlocal counter + placeholder = f'GRAFANA_PLACEHOLDER_{counter}_GRAFANA' + placeholders[placeholder] = match.group(0) + counter += 1 + return placeholder + + protected_sql = re.sub(pattern, replacer, sql) + return protected_sql, placeholders + + +def restore_grafana_variables(sql: str, placeholders: dict) -> str: + """ + Restore Grafana template variables from safe placeholders. + Also convert IN (${var}) to = ANY(ARRAY[${var}]) to handle empty variables. + PostgreSQL rejects IN () with no values, but = ANY(ARRAY[]) is valid. + """ + for placeholder, original in placeholders.items(): + sql = sql.replace(placeholder, original) + + # Convert IN (${var}) to = ANY(ARRAY[${var}]::text[]) + # Empty: column = ANY(ARRAY[]::text[]) → FALSE (typed empty array) + # Values: column = ANY(ARRAY['val1','val2']::text[]) → works correctly + sql = re.sub( + r'(\w+(?:\.\w+)?)\s+IN\s+\(\$\{([^}]+)\}\)', + r'\1 = ANY(ARRAY[${\2}]::text[])', + sql, + flags=re.IGNORECASE + ) + + return sql + + +def restore_grafana_macros(sql: str) -> str: + """ + Restore Grafana macros to lowercase after sqlglot uppercases them. + Grafana expects: $__timeFilter(), $__timeFrom(), $__timeTo() + """ + sql = re.sub(r'\$__TIMEFILTER', '$__timeFilter', sql) + sql = re.sub(r'\$__TIMEFROM', '$__timeFrom', sql) + sql = re.sub(r'\$__TIMETO', '$__timeTo', sql) + + # Fix WEEKDAY function conversion + # MySQL: DATE_SUB(DATE(x), INTERVAL WEEKDAY(DATE(x)) DAY) + # PostgreSQL: CAST(x AS DATE) - (EXTRACT(ISODOW FROM x) - 1) * INTERVAL '1 day' + # Pattern: CAST(x AS DATE) - INTERVAL 'WEEKDAY DAY' + sql = re.sub( + r'CAST\s*\(([^)]+)\s+AS\s+DATE\)\s*-\s*INTERVAL\s+[\'"]WEEKDAY\s+DAY[\'"]', + r"CAST(\1 AS DATE) - (EXTRACT(ISODOW FROM \1) - 1) * INTERVAL '1 day'", + sql, + flags=re.IGNORECASE + ) + + return sql + + +def regex_fallback_conversion(sql: str) -> str: + """ + Regex-based fallback for SQL patterns sqlglot can't handle. + Handles nested functions and computed INTERVAL expressions. + """ + # CURDATE() -> CURRENT_DATE + sql = re.sub(r'\bCURDATE\s*\(\s*\)', 'CURRENT_DATE', sql, flags=re.IGNORECASE) + + # DATE(x) -> x::date + sql = re.sub(r'\bDATE\s*\(\s*([^)]+)\s*\)', r'(\1)::date', sql, flags=re.IGNORECASE) + + # DATE_FORMAT -> TO_CHAR (basic patterns) + sql = re.sub( + r"DATE_FORMAT\s*\(\s*([^,]+),\s*'%Y-%m-%d'\s*\)", + r"TO_CHAR(\1, 'YYYY-MM-DD')", + sql, + flags=re.IGNORECASE + ) + sql = re.sub( + r"DATE_FORMAT\s*\(\s*([^,]+),\s*'%Y/%m'\s*\)", + r"TO_CHAR(\1, 'YYYY/MM')", + sql, + flags=re.IGNORECASE + ) + sql = re.sub( + r"DATE_FORMAT\s*\(\s*([^,]+),\s*'%Y-%m'\s*\)", + r"TO_CHAR(\1, 'YYYY-MM')", + sql, + flags=re.IGNORECASE + ) + + # STR_TO_DATE -> TO_DATE + sql = re.sub(r'\bSTR_TO_DATE\s*\(', 'TO_DATE(', sql, flags=re.IGNORECASE) + + # CONVERT(expr, type) -> CAST(expr AS type) + sql = re.sub( + r'CONVERT\s*\(\s*([^,]+),\s*([^)]+)\)', + r'CAST(\1 AS \2)', + sql, + flags=re.IGNORECASE + ) + + # IF(cond, a, b) -> CASE WHEN cond THEN a ELSE b END + sql = re.sub( + r'\bIF\s*\(\s*([^,]+),\s*([^,]+),\s*([^)]+)\)', + r'CASE WHEN \1 THEN \2 ELSE \3 END', + sql, + flags=re.IGNORECASE + ) + + # IFNULL -> COALESCE + sql = re.sub(r'\bIFNULL\s*\(', 'COALESCE(', sql, flags=re.IGNORECASE) + + # DATE_ADD/DATE_SUB with INTERVAL + # Handle: DATE_ADD(date, INTERVAL n DAY) -> date + INTERVAL 'n day' + sql = re.sub( + r'DATE_ADD\s*\(\s*([^,]+),\s*INTERVAL\s+([^)]+)\)', + r'\1 + INTERVAL ''\2''', + sql, + flags=re.IGNORECASE + ) + sql = re.sub( + r'DATE_SUB\s*\(\s*([^,]+),\s*INTERVAL\s+([^)]+)\)', + r'\1 - INTERVAL ''\2''', + sql, + flags=re.IGNORECASE + ) + + # Normalize INTERVAL syntax for PostgreSQL + sql = re.sub(r"INTERVAL\s+'(\d+)\s+DAY'", r"INTERVAL '\1 days'", sql, flags=re.IGNORECASE) + sql = re.sub(r"INTERVAL\s+'(\d+)\s+HOUR'", r"INTERVAL '\1 hours'", sql, flags=re.IGNORECASE) + + return sql + + +def process_dashboard_recursive(obj: Any, path: str = "") -> Any: + """ + Recursively process dashboard JSON, converting SQL and datasource references. + """ + if isinstance(obj, dict): + result = {} + for key, value in obj.items(): + current_path = f"{path}.{key}" if path else key + + # Convert rawSql fields + if key == "rawSql" and isinstance(value, str): + result[key] = convert_sql_mysql_to_postgres(value) + # Convert datasource string references + elif key == "datasource" and isinstance(value, str) and value == "mysql": + result[key] = "postgresql" + # Convert datasource object references + elif key == "datasource" and isinstance(value, dict) and value.get("type") == "mysql": + result[key] = {**value, "type": "postgres"} + # Recursively process nested objects + else: + result[key] = process_dashboard_recursive(value, current_path) + + return result + + elif isinstance(obj, list): + return [process_dashboard_recursive(item, f"{path}[{i}]") for i, item in enumerate(obj)] + + else: + return obj + + +def convert_dashboard(input_path: Path, output_path: Path) -> None: + """ + Convert a single MySQL dashboard to PostgreSQL. + """ + try: + # Read input dashboard + with open(input_path, 'r', encoding='utf-8') as f: + dashboard = json.load(f) + + # Process dashboard recursively + converted = process_dashboard_recursive(dashboard) + + # Append -pg suffix to UID to avoid collisions + if "uid" in converted and converted["uid"]: + if not converted["uid"].endswith("-pg"): + converted["uid"] = f"{converted['uid']}-pg" + + # Update title to indicate PostgreSQL variant (optional) + if "title" in converted: + if not "(PostgreSQL)" in converted["title"]: + converted["title"] = f"{converted['title']} (PostgreSQL)" + + # Write output dashboard + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(converted, f, indent=2) + + print(f"✓ Converted: {input_path.name}") + + except json.JSONDecodeError as e: + print(f"✗ ERROR: Invalid JSON in {input_path}: {e}") + sys.exit(1) + except Exception as e: + print(f"✗ ERROR converting {input_path}: {e}") + sys.exit(1) + + +def main(): + if len(sys.argv) < 3: + print("Usage: python convert-mysql-to-postgresql.py ") + print(" input_path: MySQL dashboard JSON file or directory") + print(" output_path: PostgreSQL dashboard JSON file or directory") + sys.exit(1) + + input_path = Path(sys.argv[1]) + output_path = Path(sys.argv[2]) + + if not input_path.exists(): + print(f"ERROR: Input path does not exist: {input_path}") + sys.exit(1) + + # Single file conversion + if input_path.is_file(): + convert_dashboard(input_path, output_path) + + # Directory conversion + elif input_path.is_dir(): + json_files = list(input_path.glob("*.json")) + if not json_files: + print(f"WARNING: No JSON files found in {input_path}") + return + + print(f"Converting {len(json_files)} dashboards from {input_path} to {output_path}") + + for json_file in json_files: + output_file = output_path / json_file.name + convert_dashboard(json_file, output_file) + + print(f"\n✓ Conversion complete: {len(json_files)} dashboards") + + else: + print(f"ERROR: Input path is neither file nor directory: {input_path}") + sys.exit(1) + + +if __name__ == "__main__": + main() From 305ddfbf5ee8a3cebab436a9a5386cb44e8ebddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Thu, 7 May 2026 21:14:44 -0300 Subject: [PATCH 02/11] feat: add support for postgresql to grafana dashboards --- docker-compose-dev.yml | 24 +- grafana/Dockerfile | 7 +- grafana/dashboards/AIModelROI.json | 124 ---- .../{ => mysql}/AICostEfficiency.json | 0 grafana/dashboards/mysql/AIModelROI.json | 537 ++++++++++++++++++ grafana/dashboards/{ => mysql}/ArgoCD.json | 0 grafana/dashboards/{ => mysql}/Asana.json | 0 .../dashboards/{ => mysql}/AzureDevOps.json | 0 grafana/dashboards/{ => mysql}/Bamboo.json | 0 grafana/dashboards/{ => mysql}/BitBucket.json | 0 grafana/dashboards/{ => mysql}/CircleCI.json | 0 .../ComponentAndFileLevelMetrics.json | 0 .../{ => mysql}/ContributorExperience.json | 0 grafana/dashboards/{ => mysql}/DORA.json | 0 .../dashboards/{ => mysql}/DORAByTeam.json | 0 grafana/dashboards/{ => mysql}/DORADebug.json | 0 .../DORADetails-ChangeFailureRate.json | 0 .../DORADetails-DeploymentFrequency.json | 0 ...ADetails-FailedDeploymentRecoveryTime.json | 0 .../DORADetails-LeadTimeforChanges.json | 0 .../DORADetails-TimetoRestoreService.json | 0 ...oAverageRequirementLeadTimeByAssignee.json | 0 .../{ => mysql}/DemoCommitCountByAuthor.json | 0 .../{ => mysql}/DemoDetailedBugInfo.json | 0 .../dashboards/{ => mysql}/DemoHomepage.json | 0 ...FastDoWeRespondToCustomerRequirements.json | 0 ...DemoIsThisMonthMoreProductiveThanLast.json | 0 .../DemoWasOurQualityImprovedOrNot.json | 0 .../DeveloperProductivityHours.json | 0 .../{ => mysql}/EngineeringOverview.json | 0 .../EngineeringThroughputAndCycleTime.json | 0 ...neeringThroughputAndCycleTimeTeamView.json | 0 grafana/dashboards/{ => mysql}/GitHub.json | 0 .../{ => mysql}/GithubCopilotAdoption.json | 0 .../GithubCopilotDORACorrelation.json | 0 .../{ => mysql}/GithubCopilotREADME.md | 0 ...ReleaseQualityAndContributionAnalysis.json | 0 grafana/dashboards/{ => mysql}/Gitlab.json | 0 grafana/dashboards/{ => mysql}/Homepage.json | 0 grafana/dashboards/{ => mysql}/Jenkins.json | 0 grafana/dashboards/{ => mysql}/Jira.json | 0 .../{ => mysql}/KiroCreditsDORA.json | 0 .../{ => mysql}/LanguageAIHeatmap.json | 0 .../{ => mysql}/MultiAIComparison.json | 0 grafana/dashboards/{ => mysql}/Opsgenie.json | 0 grafana/dashboards/{ => mysql}/PagerDuty.json | 0 grafana/dashboards/{ => mysql}/QDevDORA.json | 0 .../{ => mysql}/SonarQubeCloud.json | 0 grafana/dashboards/{ => mysql}/Sonarqube.json | 0 .../{ => mysql}/SteeringAdoptionTracker.json | 0 grafana/dashboards/{ => mysql}/TAPD.json | 0 grafana/dashboards/{ => mysql}/Taiga.json | 0 .../dashboards/{ => mysql}/Teambition.json | 0 grafana/dashboards/{ => mysql}/Testmo.json | 0 .../{ => mysql}/WeeklyBugRetro.json | 0 .../{ => mysql}/WeeklyCommunityRetro.json | 0 grafana/dashboards/{ => mysql}/WorkLogs.json | 0 grafana/dashboards/{ => mysql}/Zentao.json | 0 .../{ => mysql}/qdev_executive.json | 0 .../{ => mysql}/qdev_feature_metrics.json | 0 .../dashboards/{ => mysql}/qdev_logging.json | 0 .../{ => mysql}/qdev_user_data.json | 0 .../{ => mysql}/qdev_user_report.json | 0 .../postgresql/AICostEfficiency.json | 16 +- grafana/dashboards/postgresql/AIModelROI.json | 411 ++++++++++++-- .../dashboards/postgresql/AzureDevOps.json | 14 +- grafana/dashboards/postgresql/Bamboo.json | 16 +- grafana/dashboards/postgresql/BitBucket.json | 24 +- grafana/dashboards/postgresql/CircleCI.json | 16 +- .../postgresql/ContributorExperience.json | 10 +- grafana/dashboards/postgresql/DORA.json | 18 +- grafana/dashboards/postgresql/DORAByTeam.json | 18 +- grafana/dashboards/postgresql/DORADebug.json | 42 +- .../DORADetails-ChangeFailureRate.json | 8 +- .../DORADetails-DeploymentFrequency.json | 12 +- ...ADetails-FailedDeploymentRecoveryTime.json | 4 +- .../DORADetails-LeadTimeforChanges.json | 10 +- ...oAverageRequirementLeadTimeByAssignee.json | 4 +- .../postgresql/DemoCommitCountByAuthor.json | 4 +- .../postgresql/DemoDetailedBugInfo.json | 2 +- ...FastDoWeRespondToCustomerRequirements.json | 2 +- .../DemoWasOurQualityImprovedOrNot.json | 2 +- .../DeveloperProductivityHours.json | 8 +- .../postgresql/EngineeringOverview.json | 6 +- .../EngineeringThroughputAndCycleTime.json | 18 +- ...neeringThroughputAndCycleTimeTeamView.json | 22 +- grafana/dashboards/postgresql/GitHub.json | 24 +- .../GithubCopilotDORACorrelation.json | 30 +- ...ReleaseQualityAndContributionAnalysis.json | 10 +- grafana/dashboards/postgresql/Gitlab.json | 20 +- grafana/dashboards/postgresql/Jenkins.json | 16 +- .../postgresql/KiroCreditsDORA.json | 18 +- .../postgresql/LanguageAIHeatmap.json | 4 +- .../postgresql/MultiAIComparison.json | 12 +- grafana/dashboards/postgresql/Opsgenie.json | 2 +- grafana/dashboards/postgresql/QDevDORA.json | 22 +- .../dashboards/postgresql/SonarQubeCloud.json | 10 +- grafana/dashboards/postgresql/Sonarqube.json | 14 +- .../postgresql/SteeringAdoptionTracker.json | 12 +- grafana/dashboards/postgresql/Taiga.json | 6 +- .../dashboards/postgresql/WeeklyBugRetro.json | 16 +- .../postgresql/WeeklyCommunityRetro.json | 2 +- grafana/dashboards/postgresql/WorkLogs.json | 8 +- .../dashboards/postgresql/qdev_executive.json | 18 +- .../postgresql/qdev_feature_metrics.json | 18 +- .../dashboards/postgresql/qdev_logging.json | 28 +- .../dashboards/postgresql/qdev_user_data.json | 18 +- .../postgresql/qdev_user_report.json | 8 +- .../provisioning/datasources/datasource.yml | 29 - .../scripts/convert-mysql-to-postgresql.py | 119 +++- grafana/scripts/entrypoint.sh | 122 ++++ grafana/scripts/requirements.txt | 1 + 112 files changed, 1450 insertions(+), 516 deletions(-) delete mode 100644 grafana/dashboards/AIModelROI.json rename grafana/dashboards/{ => mysql}/AICostEfficiency.json (100%) create mode 100644 grafana/dashboards/mysql/AIModelROI.json rename grafana/dashboards/{ => mysql}/ArgoCD.json (100%) rename grafana/dashboards/{ => mysql}/Asana.json (100%) rename grafana/dashboards/{ => mysql}/AzureDevOps.json (100%) rename grafana/dashboards/{ => mysql}/Bamboo.json (100%) rename grafana/dashboards/{ => mysql}/BitBucket.json (100%) rename grafana/dashboards/{ => mysql}/CircleCI.json (100%) rename grafana/dashboards/{ => mysql}/ComponentAndFileLevelMetrics.json (100%) rename grafana/dashboards/{ => mysql}/ContributorExperience.json (100%) rename grafana/dashboards/{ => mysql}/DORA.json (100%) rename grafana/dashboards/{ => mysql}/DORAByTeam.json (100%) rename grafana/dashboards/{ => mysql}/DORADebug.json (100%) rename grafana/dashboards/{ => mysql}/DORADetails-ChangeFailureRate.json (100%) rename grafana/dashboards/{ => mysql}/DORADetails-DeploymentFrequency.json (100%) rename grafana/dashboards/{ => mysql}/DORADetails-FailedDeploymentRecoveryTime.json (100%) rename grafana/dashboards/{ => mysql}/DORADetails-LeadTimeforChanges.json (100%) rename grafana/dashboards/{ => mysql}/DORADetails-TimetoRestoreService.json (100%) rename grafana/dashboards/{ => mysql}/DemoAverageRequirementLeadTimeByAssignee.json (100%) rename grafana/dashboards/{ => mysql}/DemoCommitCountByAuthor.json (100%) rename grafana/dashboards/{ => mysql}/DemoDetailedBugInfo.json (100%) rename grafana/dashboards/{ => mysql}/DemoHomepage.json (100%) rename grafana/dashboards/{ => mysql}/DemoHowFastDoWeRespondToCustomerRequirements.json (100%) rename grafana/dashboards/{ => mysql}/DemoIsThisMonthMoreProductiveThanLast.json (100%) rename grafana/dashboards/{ => mysql}/DemoWasOurQualityImprovedOrNot.json (100%) rename grafana/dashboards/{ => mysql}/DeveloperProductivityHours.json (100%) rename grafana/dashboards/{ => mysql}/EngineeringOverview.json (100%) rename grafana/dashboards/{ => mysql}/EngineeringThroughputAndCycleTime.json (100%) rename grafana/dashboards/{ => mysql}/EngineeringThroughputAndCycleTimeTeamView.json (100%) rename grafana/dashboards/{ => mysql}/GitHub.json (100%) rename grafana/dashboards/{ => mysql}/GithubCopilotAdoption.json (100%) rename grafana/dashboards/{ => mysql}/GithubCopilotDORACorrelation.json (100%) rename grafana/dashboards/{ => mysql}/GithubCopilotREADME.md (100%) rename grafana/dashboards/{ => mysql}/GithubReleaseQualityAndContributionAnalysis.json (100%) rename grafana/dashboards/{ => mysql}/Gitlab.json (100%) rename grafana/dashboards/{ => mysql}/Homepage.json (100%) rename grafana/dashboards/{ => mysql}/Jenkins.json (100%) rename grafana/dashboards/{ => mysql}/Jira.json (100%) rename grafana/dashboards/{ => mysql}/KiroCreditsDORA.json (100%) rename grafana/dashboards/{ => mysql}/LanguageAIHeatmap.json (100%) rename grafana/dashboards/{ => mysql}/MultiAIComparison.json (100%) rename grafana/dashboards/{ => mysql}/Opsgenie.json (100%) rename grafana/dashboards/{ => mysql}/PagerDuty.json (100%) rename grafana/dashboards/{ => mysql}/QDevDORA.json (100%) rename grafana/dashboards/{ => mysql}/SonarQubeCloud.json (100%) rename grafana/dashboards/{ => mysql}/Sonarqube.json (100%) rename grafana/dashboards/{ => mysql}/SteeringAdoptionTracker.json (100%) rename grafana/dashboards/{ => mysql}/TAPD.json (100%) rename grafana/dashboards/{ => mysql}/Taiga.json (100%) rename grafana/dashboards/{ => mysql}/Teambition.json (100%) rename grafana/dashboards/{ => mysql}/Testmo.json (100%) rename grafana/dashboards/{ => mysql}/WeeklyBugRetro.json (100%) rename grafana/dashboards/{ => mysql}/WeeklyCommunityRetro.json (100%) rename grafana/dashboards/{ => mysql}/WorkLogs.json (100%) rename grafana/dashboards/{ => mysql}/Zentao.json (100%) rename grafana/dashboards/{ => mysql}/qdev_executive.json (100%) rename grafana/dashboards/{ => mysql}/qdev_feature_metrics.json (100%) rename grafana/dashboards/{ => mysql}/qdev_logging.json (100%) rename grafana/dashboards/{ => mysql}/qdev_user_data.json (100%) rename grafana/dashboards/{ => mysql}/qdev_user_report.json (100%) delete mode 100644 grafana/provisioning/datasources/datasource.yml create mode 100644 grafana/scripts/entrypoint.sh create mode 100644 grafana/scripts/requirements.txt diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index b575a6029ac..7f97a8f77d0 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -28,8 +28,7 @@ services: MYSQL_USER: merico MYSQL_PASSWORD: merico TZ: UTC - command: --character-set-server=utf8mb4 - --collation-server=utf8mb4_bin + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-log-bin postgres: @@ -51,7 +50,7 @@ services: - postgres17-storage:/var/lib/postgresql restart: always ports: - - 5432:5432 + - 5433:5432 environment: POSTGRES_DB: lake POSTGRES_USER: merico @@ -69,14 +68,17 @@ services: environment: GF_SERVER_ROOT_URL: "http://localhost:4000/grafana" GF_USERS_DEFAULT_THEME: "light" - MYSQL_URL: mysql:3306 - MYSQL_DATABASE: lake - MYSQL_USER: merico - MYSQL_PASSWORD: merico + # Database configuration (choose mysql or postgresql) + DATABASE_TYPE: postgresql + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_NAME: lake + DATABASE_USER: merico + DATABASE_PASSWORD: merico TZ: UTC restart: always depends_on: - - mysql + - postgres devlake: image: devlake-local:latest @@ -87,7 +89,7 @@ services: HTTPS_PROXY: "${HTTPS_PROXY}" GOPROXY: "${GOPROXY}" ports: - - 8080:8080 + - 8084:8080 restart: always volumes: - devlake-log:/app/logs @@ -102,7 +104,7 @@ services: TZ: UTC # LOGOUT_URI: https://xxx.amazoncognito.com/logout?client_id=yyy&logout_uri=http%3A%2F%2Flocalhost%3A4180%2Foauth2%2Fsign_out depends_on: - - mysql + - postgres config-ui: image: devlake.docker.scarf.sh/apache/devlake-config-ui:latest @@ -149,4 +151,4 @@ volumes: grafana-storage: postgres-storage: postgres17-storage: - devlake-log: \ No newline at end of file + devlake-log: diff --git a/grafana/Dockerfile b/grafana/Dockerfile index 406e7c9a09f..7a316b81270 100644 --- a/grafana/Dockerfile +++ b/grafana/Dockerfile @@ -25,17 +25,18 @@ FROM grafana/grafana:11.6.2 COPY ./provisioning/dashboards /etc/grafana/provisioning/dashboards -COPY ./provisioning/datasources /etc/grafana/provisioning/datasources COPY ./dashboards /etc/grafana/dashboards +COPY ./scripts/entrypoint.sh /entrypoint.sh COPY ./img/grafana_icon.svg /usr/share/grafana/public/img/grafana_icon.svg COPY ./img /usr/share/grafana/public/img/lake ENV GF_USERS_ALLOW_SIGN_UP=false ENV GF_SERVER_SERVE_FROM_SUB_PATH=true ENV GF_DASHBOARDS_JSON_ENABLED=true ENV GF_LIVE_ALLOWED_ORIGINS='*' -ENV GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/dashboards/Homepage.json USER root RUN grafana-cli plugins install grafana-piechart-panel -RUN chgrp -R 0 /etc/grafana /usr/share/grafana /var/lib/grafana && \ +RUN chmod +x /entrypoint.sh && \ + chgrp -R 0 /etc/grafana /usr/share/grafana /var/lib/grafana && \ chmod -R g=u /etc/grafana /usr/share/grafana /var/lib/grafana USER 101 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/grafana/dashboards/AIModelROI.json b/grafana/dashboards/AIModelROI.json deleted file mode 100644 index 4ee15620bb1..00000000000 --- a/grafana/dashboards/AIModelROI.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "id": null, - "links": [], - "panels": [ - { - "datasource": "mysql", - "description": "Requests, avg prompt/response length, and usage share per model", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "filterable": true }, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green" }] } - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 }, - "id": 1, - "options": { "cellHeight": "sm", "showHeader": true, "sortBy": [] }, - "targets": [ - { - "datasource": "mysql", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT\n CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS 'Model',\n COUNT(*) AS 'Requests',\n ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)), 1) AS 'Share %',\n ROUND(AVG(prompt_length)) AS 'Avg Prompt Len',\n ROUND(AVG(response_length)) AS 'Avg Response Len',\n ROUND(AVG(response_length) / NULLIF(AVG(prompt_length), 0), 2) AS 'Response/Prompt Ratio',\n SUM(CASE WHEN has_steering = 1 THEN 1 ELSE 0 END) AS 'Steering Uses',\n SUM(CASE WHEN is_spec_mode = 1 THEN 1 ELSE 0 END) AS 'Spec Mode Uses'\nFROM _tool_q_dev_chat_log\nWHERE $__timeFilter(timestamp)\nGROUP BY model_id\nORDER BY COUNT(*) DESC", - "refId": "A" - } - ], - "title": "Model Performance Summary", - "type": "table" - }, - { - "datasource": "mysql", - "description": "Request volume by model over time", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { - "drawStyle": "bars", "fillOpacity": 80, "lineWidth": 1, - "stacking": { "mode": "normal" }, "thresholdsStyle": { "mode": "off" } - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 24, "x": 0, "y": 8 }, - "id": 2, - "options": { - "legend": { "calcs": ["sum"], "displayMode": "table", "placement": "right", "showLegend": true }, - "tooltip": { "mode": "multi" } - }, - "targets": [ - { - "datasource": "mysql", - "format": "time_series", - "rawQuery": true, - "rawSql": "SELECT DATE(timestamp) AS time,\n CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric,\n COUNT(*) AS value\nFROM _tool_q_dev_chat_log\nWHERE $__timeFilter(timestamp)\nGROUP BY DATE(timestamp), model_id\nORDER BY time", - "refId": "A" - } - ], - "title": "Daily Model Usage Distribution", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "Average response length per model over time — proxy for output quality", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { - "drawStyle": "line", "fillOpacity": 10, "lineInterpolation": "smooth", "lineWidth": 2, - "showPoints": "never", "spanNulls": true, - "stacking": { "mode": "none" }, "thresholdsStyle": { "mode": "off" } - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 24, "x": 0, "y": 16 }, - "id": 3, - "options": { - "legend": { "calcs": ["mean"], "displayMode": "table", "placement": "right", "showLegend": true }, - "tooltip": { "mode": "multi" } - }, - "targets": [ - { - "datasource": "mysql", - "format": "time_series", - "rawQuery": true, - "rawSql": "SELECT DATE(timestamp) AS time,\n CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric,\n ROUND(AVG(response_length)) AS value\nFROM _tool_q_dev_chat_log\nWHERE $__timeFilter(timestamp)\nGROUP BY DATE(timestamp), model_id\nORDER BY time", - "refId": "A" - } - ], - "title": "Avg Response Length by Model (Daily)", - "type": "timeseries" - } - ], - "preload": false, - "refresh": "5m", - "schemaVersion": 41, - "tags": ["q_dev", "kiro", "model", "roi"], - "templating": { "list": [] }, - "time": { "from": "now-90d", "to": "now" }, - "timepicker": {}, - "timezone": "utc", - "title": "Kiro AI Model ROI", - "uid": "kiro_model_roi", - "version": 1 -} diff --git a/grafana/dashboards/AICostEfficiency.json b/grafana/dashboards/mysql/AICostEfficiency.json similarity index 100% rename from grafana/dashboards/AICostEfficiency.json rename to grafana/dashboards/mysql/AICostEfficiency.json diff --git a/grafana/dashboards/mysql/AIModelROI.json b/grafana/dashboards/mysql/AIModelROI.json new file mode 100644 index 00000000000..49980a1fadb --- /dev/null +++ b/grafana/dashboards/mysql/AIModelROI.json @@ -0,0 +1,537 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Kiro Usage Dashboard", + "type": "link", + "url": "/d/qdev_user_report" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Summary", + "type": "row" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(SUM(credits_used)) AS \"Total Credits\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Total Credits Used", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per PR (Overall)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per Deployment (Overall)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 10, + "panels": [], + "title": "Weekly Trends", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per merged PR", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(p.prs, 0), 0), 1) AS \"Credits per PR\" FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Merged PR (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per production deployment", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(d.deploys, 0), 0), 1) AS \"Credits per Deploy\" FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Deployment (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per resolved issue", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(i.resolved, 0), 0), 1) AS \"Credits per Issue\" FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Is cost efficiency improving over time?", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Weekly AI Activity Volume", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 41, + "tags": [ + "q_dev", + "kiro", + "cost", + "efficiency" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-90d", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "AI Cost-Efficiency (PostgreSQL)", + "uid": "ai_cost_efficiency-pg", + "version": 1 +} \ No newline at end of file diff --git a/grafana/dashboards/ArgoCD.json b/grafana/dashboards/mysql/ArgoCD.json similarity index 100% rename from grafana/dashboards/ArgoCD.json rename to grafana/dashboards/mysql/ArgoCD.json diff --git a/grafana/dashboards/Asana.json b/grafana/dashboards/mysql/Asana.json similarity index 100% rename from grafana/dashboards/Asana.json rename to grafana/dashboards/mysql/Asana.json diff --git a/grafana/dashboards/AzureDevOps.json b/grafana/dashboards/mysql/AzureDevOps.json similarity index 100% rename from grafana/dashboards/AzureDevOps.json rename to grafana/dashboards/mysql/AzureDevOps.json diff --git a/grafana/dashboards/Bamboo.json b/grafana/dashboards/mysql/Bamboo.json similarity index 100% rename from grafana/dashboards/Bamboo.json rename to grafana/dashboards/mysql/Bamboo.json diff --git a/grafana/dashboards/BitBucket.json b/grafana/dashboards/mysql/BitBucket.json similarity index 100% rename from grafana/dashboards/BitBucket.json rename to grafana/dashboards/mysql/BitBucket.json diff --git a/grafana/dashboards/CircleCI.json b/grafana/dashboards/mysql/CircleCI.json similarity index 100% rename from grafana/dashboards/CircleCI.json rename to grafana/dashboards/mysql/CircleCI.json diff --git a/grafana/dashboards/ComponentAndFileLevelMetrics.json b/grafana/dashboards/mysql/ComponentAndFileLevelMetrics.json similarity index 100% rename from grafana/dashboards/ComponentAndFileLevelMetrics.json rename to grafana/dashboards/mysql/ComponentAndFileLevelMetrics.json diff --git a/grafana/dashboards/ContributorExperience.json b/grafana/dashboards/mysql/ContributorExperience.json similarity index 100% rename from grafana/dashboards/ContributorExperience.json rename to grafana/dashboards/mysql/ContributorExperience.json diff --git a/grafana/dashboards/DORA.json b/grafana/dashboards/mysql/DORA.json similarity index 100% rename from grafana/dashboards/DORA.json rename to grafana/dashboards/mysql/DORA.json diff --git a/grafana/dashboards/DORAByTeam.json b/grafana/dashboards/mysql/DORAByTeam.json similarity index 100% rename from grafana/dashboards/DORAByTeam.json rename to grafana/dashboards/mysql/DORAByTeam.json diff --git a/grafana/dashboards/DORADebug.json b/grafana/dashboards/mysql/DORADebug.json similarity index 100% rename from grafana/dashboards/DORADebug.json rename to grafana/dashboards/mysql/DORADebug.json diff --git a/grafana/dashboards/DORADetails-ChangeFailureRate.json b/grafana/dashboards/mysql/DORADetails-ChangeFailureRate.json similarity index 100% rename from grafana/dashboards/DORADetails-ChangeFailureRate.json rename to grafana/dashboards/mysql/DORADetails-ChangeFailureRate.json diff --git a/grafana/dashboards/DORADetails-DeploymentFrequency.json b/grafana/dashboards/mysql/DORADetails-DeploymentFrequency.json similarity index 100% rename from grafana/dashboards/DORADetails-DeploymentFrequency.json rename to grafana/dashboards/mysql/DORADetails-DeploymentFrequency.json diff --git a/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/mysql/DORADetails-FailedDeploymentRecoveryTime.json similarity index 100% rename from grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json rename to grafana/dashboards/mysql/DORADetails-FailedDeploymentRecoveryTime.json diff --git a/grafana/dashboards/DORADetails-LeadTimeforChanges.json b/grafana/dashboards/mysql/DORADetails-LeadTimeforChanges.json similarity index 100% rename from grafana/dashboards/DORADetails-LeadTimeforChanges.json rename to grafana/dashboards/mysql/DORADetails-LeadTimeforChanges.json diff --git a/grafana/dashboards/DORADetails-TimetoRestoreService.json b/grafana/dashboards/mysql/DORADetails-TimetoRestoreService.json similarity index 100% rename from grafana/dashboards/DORADetails-TimetoRestoreService.json rename to grafana/dashboards/mysql/DORADetails-TimetoRestoreService.json diff --git a/grafana/dashboards/DemoAverageRequirementLeadTimeByAssignee.json b/grafana/dashboards/mysql/DemoAverageRequirementLeadTimeByAssignee.json similarity index 100% rename from grafana/dashboards/DemoAverageRequirementLeadTimeByAssignee.json rename to grafana/dashboards/mysql/DemoAverageRequirementLeadTimeByAssignee.json diff --git a/grafana/dashboards/DemoCommitCountByAuthor.json b/grafana/dashboards/mysql/DemoCommitCountByAuthor.json similarity index 100% rename from grafana/dashboards/DemoCommitCountByAuthor.json rename to grafana/dashboards/mysql/DemoCommitCountByAuthor.json diff --git a/grafana/dashboards/DemoDetailedBugInfo.json b/grafana/dashboards/mysql/DemoDetailedBugInfo.json similarity index 100% rename from grafana/dashboards/DemoDetailedBugInfo.json rename to grafana/dashboards/mysql/DemoDetailedBugInfo.json diff --git a/grafana/dashboards/DemoHomepage.json b/grafana/dashboards/mysql/DemoHomepage.json similarity index 100% rename from grafana/dashboards/DemoHomepage.json rename to grafana/dashboards/mysql/DemoHomepage.json diff --git a/grafana/dashboards/DemoHowFastDoWeRespondToCustomerRequirements.json b/grafana/dashboards/mysql/DemoHowFastDoWeRespondToCustomerRequirements.json similarity index 100% rename from grafana/dashboards/DemoHowFastDoWeRespondToCustomerRequirements.json rename to grafana/dashboards/mysql/DemoHowFastDoWeRespondToCustomerRequirements.json diff --git a/grafana/dashboards/DemoIsThisMonthMoreProductiveThanLast.json b/grafana/dashboards/mysql/DemoIsThisMonthMoreProductiveThanLast.json similarity index 100% rename from grafana/dashboards/DemoIsThisMonthMoreProductiveThanLast.json rename to grafana/dashboards/mysql/DemoIsThisMonthMoreProductiveThanLast.json diff --git a/grafana/dashboards/DemoWasOurQualityImprovedOrNot.json b/grafana/dashboards/mysql/DemoWasOurQualityImprovedOrNot.json similarity index 100% rename from grafana/dashboards/DemoWasOurQualityImprovedOrNot.json rename to grafana/dashboards/mysql/DemoWasOurQualityImprovedOrNot.json diff --git a/grafana/dashboards/DeveloperProductivityHours.json b/grafana/dashboards/mysql/DeveloperProductivityHours.json similarity index 100% rename from grafana/dashboards/DeveloperProductivityHours.json rename to grafana/dashboards/mysql/DeveloperProductivityHours.json diff --git a/grafana/dashboards/EngineeringOverview.json b/grafana/dashboards/mysql/EngineeringOverview.json similarity index 100% rename from grafana/dashboards/EngineeringOverview.json rename to grafana/dashboards/mysql/EngineeringOverview.json diff --git a/grafana/dashboards/EngineeringThroughputAndCycleTime.json b/grafana/dashboards/mysql/EngineeringThroughputAndCycleTime.json similarity index 100% rename from grafana/dashboards/EngineeringThroughputAndCycleTime.json rename to grafana/dashboards/mysql/EngineeringThroughputAndCycleTime.json diff --git a/grafana/dashboards/EngineeringThroughputAndCycleTimeTeamView.json b/grafana/dashboards/mysql/EngineeringThroughputAndCycleTimeTeamView.json similarity index 100% rename from grafana/dashboards/EngineeringThroughputAndCycleTimeTeamView.json rename to grafana/dashboards/mysql/EngineeringThroughputAndCycleTimeTeamView.json diff --git a/grafana/dashboards/GitHub.json b/grafana/dashboards/mysql/GitHub.json similarity index 100% rename from grafana/dashboards/GitHub.json rename to grafana/dashboards/mysql/GitHub.json diff --git a/grafana/dashboards/GithubCopilotAdoption.json b/grafana/dashboards/mysql/GithubCopilotAdoption.json similarity index 100% rename from grafana/dashboards/GithubCopilotAdoption.json rename to grafana/dashboards/mysql/GithubCopilotAdoption.json diff --git a/grafana/dashboards/GithubCopilotDORACorrelation.json b/grafana/dashboards/mysql/GithubCopilotDORACorrelation.json similarity index 100% rename from grafana/dashboards/GithubCopilotDORACorrelation.json rename to grafana/dashboards/mysql/GithubCopilotDORACorrelation.json diff --git a/grafana/dashboards/GithubCopilotREADME.md b/grafana/dashboards/mysql/GithubCopilotREADME.md similarity index 100% rename from grafana/dashboards/GithubCopilotREADME.md rename to grafana/dashboards/mysql/GithubCopilotREADME.md diff --git a/grafana/dashboards/GithubReleaseQualityAndContributionAnalysis.json b/grafana/dashboards/mysql/GithubReleaseQualityAndContributionAnalysis.json similarity index 100% rename from grafana/dashboards/GithubReleaseQualityAndContributionAnalysis.json rename to grafana/dashboards/mysql/GithubReleaseQualityAndContributionAnalysis.json diff --git a/grafana/dashboards/Gitlab.json b/grafana/dashboards/mysql/Gitlab.json similarity index 100% rename from grafana/dashboards/Gitlab.json rename to grafana/dashboards/mysql/Gitlab.json diff --git a/grafana/dashboards/Homepage.json b/grafana/dashboards/mysql/Homepage.json similarity index 100% rename from grafana/dashboards/Homepage.json rename to grafana/dashboards/mysql/Homepage.json diff --git a/grafana/dashboards/Jenkins.json b/grafana/dashboards/mysql/Jenkins.json similarity index 100% rename from grafana/dashboards/Jenkins.json rename to grafana/dashboards/mysql/Jenkins.json diff --git a/grafana/dashboards/Jira.json b/grafana/dashboards/mysql/Jira.json similarity index 100% rename from grafana/dashboards/Jira.json rename to grafana/dashboards/mysql/Jira.json diff --git a/grafana/dashboards/KiroCreditsDORA.json b/grafana/dashboards/mysql/KiroCreditsDORA.json similarity index 100% rename from grafana/dashboards/KiroCreditsDORA.json rename to grafana/dashboards/mysql/KiroCreditsDORA.json diff --git a/grafana/dashboards/LanguageAIHeatmap.json b/grafana/dashboards/mysql/LanguageAIHeatmap.json similarity index 100% rename from grafana/dashboards/LanguageAIHeatmap.json rename to grafana/dashboards/mysql/LanguageAIHeatmap.json diff --git a/grafana/dashboards/MultiAIComparison.json b/grafana/dashboards/mysql/MultiAIComparison.json similarity index 100% rename from grafana/dashboards/MultiAIComparison.json rename to grafana/dashboards/mysql/MultiAIComparison.json diff --git a/grafana/dashboards/Opsgenie.json b/grafana/dashboards/mysql/Opsgenie.json similarity index 100% rename from grafana/dashboards/Opsgenie.json rename to grafana/dashboards/mysql/Opsgenie.json diff --git a/grafana/dashboards/PagerDuty.json b/grafana/dashboards/mysql/PagerDuty.json similarity index 100% rename from grafana/dashboards/PagerDuty.json rename to grafana/dashboards/mysql/PagerDuty.json diff --git a/grafana/dashboards/QDevDORA.json b/grafana/dashboards/mysql/QDevDORA.json similarity index 100% rename from grafana/dashboards/QDevDORA.json rename to grafana/dashboards/mysql/QDevDORA.json diff --git a/grafana/dashboards/SonarQubeCloud.json b/grafana/dashboards/mysql/SonarQubeCloud.json similarity index 100% rename from grafana/dashboards/SonarQubeCloud.json rename to grafana/dashboards/mysql/SonarQubeCloud.json diff --git a/grafana/dashboards/Sonarqube.json b/grafana/dashboards/mysql/Sonarqube.json similarity index 100% rename from grafana/dashboards/Sonarqube.json rename to grafana/dashboards/mysql/Sonarqube.json diff --git a/grafana/dashboards/SteeringAdoptionTracker.json b/grafana/dashboards/mysql/SteeringAdoptionTracker.json similarity index 100% rename from grafana/dashboards/SteeringAdoptionTracker.json rename to grafana/dashboards/mysql/SteeringAdoptionTracker.json diff --git a/grafana/dashboards/TAPD.json b/grafana/dashboards/mysql/TAPD.json similarity index 100% rename from grafana/dashboards/TAPD.json rename to grafana/dashboards/mysql/TAPD.json diff --git a/grafana/dashboards/Taiga.json b/grafana/dashboards/mysql/Taiga.json similarity index 100% rename from grafana/dashboards/Taiga.json rename to grafana/dashboards/mysql/Taiga.json diff --git a/grafana/dashboards/Teambition.json b/grafana/dashboards/mysql/Teambition.json similarity index 100% rename from grafana/dashboards/Teambition.json rename to grafana/dashboards/mysql/Teambition.json diff --git a/grafana/dashboards/Testmo.json b/grafana/dashboards/mysql/Testmo.json similarity index 100% rename from grafana/dashboards/Testmo.json rename to grafana/dashboards/mysql/Testmo.json diff --git a/grafana/dashboards/WeeklyBugRetro.json b/grafana/dashboards/mysql/WeeklyBugRetro.json similarity index 100% rename from grafana/dashboards/WeeklyBugRetro.json rename to grafana/dashboards/mysql/WeeklyBugRetro.json diff --git a/grafana/dashboards/WeeklyCommunityRetro.json b/grafana/dashboards/mysql/WeeklyCommunityRetro.json similarity index 100% rename from grafana/dashboards/WeeklyCommunityRetro.json rename to grafana/dashboards/mysql/WeeklyCommunityRetro.json diff --git a/grafana/dashboards/WorkLogs.json b/grafana/dashboards/mysql/WorkLogs.json similarity index 100% rename from grafana/dashboards/WorkLogs.json rename to grafana/dashboards/mysql/WorkLogs.json diff --git a/grafana/dashboards/Zentao.json b/grafana/dashboards/mysql/Zentao.json similarity index 100% rename from grafana/dashboards/Zentao.json rename to grafana/dashboards/mysql/Zentao.json diff --git a/grafana/dashboards/qdev_executive.json b/grafana/dashboards/mysql/qdev_executive.json similarity index 100% rename from grafana/dashboards/qdev_executive.json rename to grafana/dashboards/mysql/qdev_executive.json diff --git a/grafana/dashboards/qdev_feature_metrics.json b/grafana/dashboards/mysql/qdev_feature_metrics.json similarity index 100% rename from grafana/dashboards/qdev_feature_metrics.json rename to grafana/dashboards/mysql/qdev_feature_metrics.json diff --git a/grafana/dashboards/qdev_logging.json b/grafana/dashboards/mysql/qdev_logging.json similarity index 100% rename from grafana/dashboards/qdev_logging.json rename to grafana/dashboards/mysql/qdev_logging.json diff --git a/grafana/dashboards/qdev_user_data.json b/grafana/dashboards/mysql/qdev_user_data.json similarity index 100% rename from grafana/dashboards/qdev_user_data.json rename to grafana/dashboards/mysql/qdev_user_data.json diff --git a/grafana/dashboards/qdev_user_report.json b/grafana/dashboards/mysql/qdev_user_report.json similarity index 100% rename from grafana/dashboards/qdev_user_report.json rename to grafana/dashboards/mysql/qdev_user_report.json diff --git a/grafana/dashboards/postgresql/AICostEfficiency.json b/grafana/dashboards/postgresql/AICostEfficiency.json index 50e462020c0..49980a1fadb 100644 --- a/grafana/dashboards/postgresql/AICostEfficiency.json +++ b/grafana/dashboards/postgresql/AICostEfficiency.json @@ -87,7 +87,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(SUM(credits_used)) AS 'Total Credits' FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "rawSql": "SELECT ROUND(SUM(credits_used)) AS \"Total Credits\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", "refId": "A" } ], @@ -138,7 +138,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 1) AS 'Credits / PR' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -189,7 +189,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 1) AS 'Credits / Deploy' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -240,7 +240,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 1) AS 'Credits / Issue' FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -316,7 +316,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(p.prs, 0), 0), 1) AS 'Credits per PR' FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(p.prs, 0), 0), 1) AS \"Credits per PR\" FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -379,7 +379,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(d.deploys, 0), 0), 1) AS 'Credits per Deploy' FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(d.deploys, 0), 0), 1) AS \"Credits per Deploy\" FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -442,7 +442,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(i.resolved, 0), 0), 1) AS 'Credits per Issue' FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(i.resolved, 0), 0), 1) AS \"Credits per Issue\" FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -505,7 +505,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS 'Credits', SUM(total_messages) AS 'Messages', SUM(chat_conversations) AS 'Conversations' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/AIModelROI.json b/grafana/dashboards/postgresql/AIModelROI.json index e0759d89d7b..b8848db01c7 100644 --- a/grafana/dashboards/postgresql/AIModelROI.json +++ b/grafana/dashboards/postgresql/AIModelROI.json @@ -16,28 +16,47 @@ "fiscalYearStartMonth": 0, "graphTooltip": 1, "id": null, - "links": [], + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [], + "targetBlank": true, + "title": "Kiro Usage Dashboard", + "type": "link", + "url": "/d/qdev_user_report" + } + ], "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Summary", + "type": "row" + }, { "datasource": "postgresql", - "description": "Requests, avg prompt/response length, and usage share per model", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "filterable": true - }, + "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" + "color": "blue", + "value": null } ] } @@ -45,43 +64,223 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 24, + "h": 6, + "w": 6, "x": 0, - "y": 0 + "y": 1 }, - "id": 1, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(SUM(credits_used)) AS \"Total Credits\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "refId": "A" + } + ], + "title": "Total Credits Used", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per PR (Overall)", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 4, "options": { - "cellHeight": "sm", - "showHeader": true, - "sortBy": [] + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" }, "targets": [ { "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS 'Model', COUNT(*) AS 'Requests', ROUND(CAST(COUNT(*) * 100.0 AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)), 0), 1) AS 'Share %', ROUND(AVG(prompt_length)) AS 'Avg Prompt Len', ROUND(AVG(response_length)) AS 'Avg Response Len', ROUND(CAST(AVG(response_length) AS NUMERIC) / NULLIF(NULLIF(AVG(prompt_length), 0), 0), 2) AS 'Response/Prompt Ratio', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering Uses', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode Uses' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", "refId": "A" } ], - "title": "Model Performance Summary", - "type": "table" + "title": "Credits per Deployment (Overall)", + "type": "stat" }, { "datasource": "postgresql", - "description": "Request volume by model over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": "postgresql", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 10, + "panels": [], + "title": "Weekly Trends", + "type": "row" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per merged PR", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { - "drawStyle": "bars", - "fillOpacity": 80, - "lineWidth": 1, + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, "stacking": { - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" @@ -93,15 +292,16 @@ }, "gridPos": { "h": 8, - "w": 24, + "w": 12, "x": 0, "y": 8 }, - "id": 2, + "id": 11, "options": { "legend": { "calcs": [ - "sum" + "mean", + "min" ], "displayMode": "table", "placement": "right", @@ -116,26 +316,30 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric, COUNT(*) AS value FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), model_id ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(p.prs, 0), 0), 0), 1) AS \"Credits per PR\" FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], - "title": "Daily Model Usage Distribution", + "title": "Credits per Merged PR (Weekly)", "type": "timeseries" }, { "datasource": "postgresql", - "description": "Average response length per model over time \u2014 proxy for output quality", + "description": "Weekly cost per production deployment", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", "drawStyle": "line", "fillOpacity": 10, "lineInterpolation": "smooth", "lineWidth": 2, + "pointSize": 5, "showPoints": "never", "spanNulls": true, "stacking": { @@ -151,15 +355,142 @@ }, "gridPos": { "h": 8, - "w": 24, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(d.deploys, 0), 0), 0), 1) AS \"Credits per Deploy\" FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Deployment (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Weekly cost per resolved issue", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, "x": 0, "y": 16 }, - "id": 3, + "id": 13, "options": { "legend": { "calcs": [ - "mean" + "mean", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": "postgresql", + "format": "time_series", + "rawQuery": true, + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(i.resolved, 0), 0), 0), 1) AS \"Credits per Issue\" FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", + "refId": "A" + } + ], + "title": "Credits per Issue Resolved (Weekly)", + "type": "timeseries" + }, + { + "datasource": "postgresql", + "description": "Is cost efficiency improving over time?", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "mean", + "sum" ], "displayMode": "table", "placement": "right", @@ -174,11 +505,11 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS metric, ROUND(AVG(response_length)) AS value FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), model_id ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL STRING_LITERAL_0_PLACEHOLDER AS time, SUM(credits_used) AS STRING_LITERAL_1_PLACEHOLDER, SUM(total_messages) AS STRING_LITERAL_2_PLACEHOLDER, SUM(chat_conversations) AS STRING_LITERAL_3_PLACEHOLDER FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL STRING_LITERAL_4_PLACEHOLDER ORDER BY time NULLS FIRST", "refId": "A" } ], - "title": "Avg Response Length by Model (Daily)", + "title": "Weekly AI Activity Volume", "type": "timeseries" } ], @@ -188,8 +519,8 @@ "tags": [ "q_dev", "kiro", - "model", - "roi" + "cost", + "efficiency" ], "templating": { "list": [] @@ -200,7 +531,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro AI Model ROI (PostgreSQL)", - "uid": "kiro_model_roi-pg", + "title": "AI Cost-Efficiency (PostgreSQL)", + "uid": "ai_cost_efficiency-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/AzureDevOps.json b/grafana/dashboards/postgresql/AzureDevOps.json index 6f6fdd3351d..ef633ebcfe0 100644 --- a/grafana/dashboards/postgresql/AzureDevOps.json +++ b/grafana/dashboards/postgresql/AzureDevOps.json @@ -440,7 +440,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -572,7 +572,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id like \"%azure%\" */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id like \"%azure%\" */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/Bamboo.json b/grafana/dashboards/postgresql/Bamboo.json index e4a202554cf..674a1037309 100644 --- a/grafana/dashboards/postgresql/Bamboo.json +++ b/grafana/dashboards/postgresql/Bamboo.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -511,7 +511,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -649,7 +649,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -806,7 +806,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -973,7 +973,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1109,7 +1109,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%bamboo%\" AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/BitBucket.json b/grafana/dashboards/postgresql/BitBucket.json index 31e240eba6f..0ed1342cee8 100644 --- a/grafana/dashboards/postgresql/BitBucket.json +++ b/grafana/dashboards/postgresql/BitBucket.json @@ -443,7 +443,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -575,7 +575,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -1319,7 +1319,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1426,7 +1426,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1594,7 +1594,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1781,7 +1781,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2023,7 +2023,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"bitbucket%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/CircleCI.json b/grafana/dashboards/postgresql/CircleCI.json index 4213b03c6f9..36ad98bdafd 100644 --- a/grafana/dashboards/postgresql/CircleCI.json +++ b/grafana/dashboards/postgresql/CircleCI.json @@ -122,7 +122,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", "refId": "A", "select": [ [ @@ -227,7 +227,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", "refId": "A", "select": [ [ @@ -415,7 +415,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", "refId": "A", "select": [ [ @@ -658,7 +658,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -817,7 +817,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -988,7 +988,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -1144,7 +1144,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%circleci%\" AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/ContributorExperience.json b/grafana/dashboards/postgresql/ContributorExperience.json index 5aa0a98cbc9..25b4c94f7ce 100644 --- a/grafana/dashboards/postgresql/ContributorExperience.json +++ b/grafana/dashboards/postgresql/ContributorExperience.json @@ -201,7 +201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = \"DONE\" AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -369,7 +369,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = \"$label_gfi\" AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = '$label_gfi' AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -554,7 +554,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -639,7 +639,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * SUM(CASE WHEN CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0) < $prrt_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -747,7 +747,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DORA.json b/grafana/dashboards/postgresql/DORA.json index 0b044b968f1..8b042f4b83f 100644 --- a/grafana/dashboards/postgresql/DORA.json +++ b/grafana/dashboards/postgresql/DORA.json @@ -233,7 +233,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n SELECT\n cdc.cicd_deployment_id as deployment_id,\n max(DATE(cdc.finished_date)) as day\n FROM\n cicd_deployment_commits cdc\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id\n and pm.`table` = 'cicd_scopes'\n WHERE\n pm.project_name in (${project})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY\n 1\n),\n_days_weekly_deploy as(\n -- calculate the number of deployment days every week\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - WEEKDAY(last_few_calendar_months.day) DAY\n )\n ) as week,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n 0\n )\n ) as weeks_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n week\n),\n_days_monthly_deploy as(\n -- calculate the number of deployment days every month\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - DAY(last_few_calendar_months.day) + 1 DAY\n )\n ) as month,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n null\n )\n ) as months_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n month\n),\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY\n month DESC\n ) AS rn\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_week_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_weekly_deploy\n),\n_median_number_of_deployment_days_per_week as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_week\n FROM\n _median_number_of_deployment_days_per_week_ranks\n WHERE\n ranks <= 0.5\n),\n_median_number_of_deployment_days_per_month_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_month as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_month\n FROM\n _median_number_of_deployment_days_per_month_ranks\n WHERE\n ranks <= 0.5\n),\n_days_per_six_months_deploy_by_filter AS (\n SELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\n FROM\n _days_six_months_deploy\n WHERE\n rn % 6 = 1\n),\n_median_number_of_deployment_days_per_six_months_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed_per_six_months\n ) as ranks\n FROM\n _days_per_six_months_deploy_by_filter\n),\n_median_number_of_deployment_days_per_six_months as(\n SELECT\n min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months,\n min(months_deployed_count) as is_collected\n FROM\n _median_number_of_deployment_days_per_six_months_ranks\n WHERE\n ranks >= 0.5\n),\n_metric_deployment_frequency as (\n SELECT\n 'Deployment frequency' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n WHEN median_number_of_deployment_days_per_month < 1\n and is_collected is not null THEN 'Fewer than once per month(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n WHEN median_number_of_deployment_days_per_six_months < 1\n and is_collected is not null THEN 'Fewer than once per six months(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_number_of_deployment_days_per_week,\n _median_number_of_deployment_days_per_month,\n _median_number_of_deployment_days_per_six_months\n),\n-- Metric 2: median lead time for changes\n_pr_stats as (\n -- get the cycle time of PRs deployed by the deployments finished in the selected period\n SELECT\n distinct pr.id,\n ppm.pr_cycle_time\n FROM\n pull_requests pr\n join project_pr_metrics ppm on ppm.id = pr.id\n join project_mapping pm on pr.base_repo_id = pm.row_id\n and pm.`table` = 'repos'\n join cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n WHERE\n pm.project_name in (${project})\n and pr.merged_date is not null\n and ppm.pr_cycle_time is not null\n and $__timeFilter(cdc.finished_date)\n),\n_median_change_lead_time_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n pr_cycle_time\n ) as ranks\n FROM\n _pr_stats\n),\n_median_change_lead_time as(\n -- use median PR cycle time as the median change lead time\n SELECT\n max(pr_cycle_time) as median_change_lead_time\n FROM\n _median_change_lead_time_ranks\n WHERE\n ranks <= 0.5\n),\n_metric_change_lead_time as (\n SELECT\n 'Lead time for changes' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_change_lead_time\n),\n-- Metric 3: change failure rate\n_deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.05 THEN \"0-5%(elite)\" WHEN change_failure_rate <= 0.10 THEN \"5%-10%(high)\" WHEN change_failure_rate <= 0.15 THEN \"10%-15%(medium)\" WHEN change_failure_rate > 0.15 THEN \"> 15%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.15 THEN \"0-15%(elite)\" WHEN change_failure_rate <= 0.20 THEN \"16%-20%(high)\" WHEN change_failure_rate <= 0.30 THEN \"21%-30%(medium)\" WHEN change_failure_rate > 0.30 THEN \"> 30%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\" WHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\" WHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", "refId": "A", "select": [ [ @@ -380,7 +380,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_7_PLACEHOLDER deployment days per week(high)STRING_LITERAL_8_PLACEHOLDER deployment days per month(medium)STRING_LITERAL_9_PLACEHOLDER deployment days per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_13_PLACEHOLDER deployment days per month(high)STRING_LITERAL_14_PLACEHOLDER deployment days per six months(medium)STRING_LITERAL_15_PLACEHOLDER deployment days per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_week >= 1 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(high)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(medium)') WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(low)') ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(high)') WHEN median_number_of_deployment_days_per_six_months >= 1 THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(medium)') WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(low)') ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -526,7 +526,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -673,7 +673,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERNo dataSTRING_LITERAL_11_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.10 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.20 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'No data' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", "refId": "A", "select": [ [ @@ -827,7 +827,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"No data\" END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -962,7 +962,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -1095,7 +1095,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hours' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hours\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -1249,7 +1249,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -1407,7 +1407,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DORAByTeam.json b/grafana/dashboards/postgresql/DORAByTeam.json index 03c39ee9346..8831b86c98f 100644 --- a/grafana/dashboards/postgresql/DORAByTeam.json +++ b/grafana/dashboards/postgresql/DORAByTeam.json @@ -206,7 +206,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n SELECT\n cdc.cicd_deployment_id as deployment_id,\n max(DATE(cdc.finished_date)) as day\n FROM\n cicd_deployment_commits cdc\n JOIN commits c on cdc.commit_sha = c.sha\n join user_accounts ua on c.author_id = ua.account_id\n join users u on ua.user_id = u.id\n join team_users tu on u.id = tu.user_id\n join teams t on tu.team_id = t.id\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id\n and pm.`table` = 'cicd_scopes'\n WHERE\n t.name in (${team})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY\n 1\n),\n_days_weekly_deploy as(\n -- calculate the number of deployment days every week\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - WEEKDAY(last_few_calendar_months.day) DAY\n )\n ) as week,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n 0\n )\n ) as weeks_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n week\n),\n_days_monthly_deploy as(\n -- calculate the number of deployment days every month\n SELECT\n date(\n DATE_ADD(\n last_few_calendar_months.day,\n INTERVAL - DAY(last_few_calendar_months.day) + 1 DAY\n )\n ) as month,\n MAX(\n if(\n _production_deployment_days.day is not null,\n 1,\n null\n )\n ) as months_deployed,\n COUNT(distinct _production_deployment_days.day) as days_deployed\n FROM\n last_few_calendar_months\n LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n GROUP BY\n month\n),\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY\n month ROWS BETWEEN 5 PRECEDING\n AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY\n month DESC\n ) AS rn\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_week_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_weekly_deploy\n),\n_median_number_of_deployment_days_per_week as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_week\n FROM\n _median_number_of_deployment_days_per_week_ranks\n WHERE\n ranks <= 0.5\n),\n_median_number_of_deployment_days_per_month_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed\n ) as ranks\n FROM\n _days_monthly_deploy\n),\n_median_number_of_deployment_days_per_month as(\n SELECT\n max(days_deployed) as median_number_of_deployment_days_per_month\n FROM\n _median_number_of_deployment_days_per_month_ranks\n WHERE\n ranks <= 0.5\n),\n_days_per_six_months_deploy_by_filter AS (\n SELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\n FROM\n _days_six_months_deploy\n WHERE\n rn % 6 = 1\n),\n_median_number_of_deployment_days_per_six_months_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n days_deployed_per_six_months\n ) as ranks\n FROM\n _days_per_six_months_deploy_by_filter\n),\n_median_number_of_deployment_days_per_six_months as(\n SELECT\n min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months,\n min(months_deployed_count) as is_collected\n FROM\n _median_number_of_deployment_days_per_six_months_ranks\n WHERE\n ranks >= 0.5\n),\n_metric_deployment_frequency as (\n SELECT\n 'Deployment frequency' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n WHEN median_number_of_deployment_days_per_month < 1\n and is_collected is not null THEN 'Fewer than once per month(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)'\n WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n WHEN median_number_of_deployment_days_per_six_months < 1\n and is_collected is not null THEN 'Fewer than once per six months(low)'\n ELSE \"N/A. Please check if you have collected deployments.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_number_of_deployment_days_per_week,\n _median_number_of_deployment_days_per_month,\n _median_number_of_deployment_days_per_six_months\n),\n-- Metric 2: median lead time for changes\n_pr_stats as (\n -- get the cycle time of PRs deployed by the deployments finished in the selected period\n SELECT\n distinct pr.id,\n ppm.pr_cycle_time\n FROM\n pull_requests pr\n join user_accounts ua on pr.author_id = ua.account_id\n join users u on ua.user_id = u.id\n join team_users tu on u.id = tu.user_id\n join teams t on tu.team_id = t.id\n join project_pr_metrics ppm on ppm.id = pr.id\n join project_mapping pm on pr.base_repo_id = pm.row_id\n and pm.`table` = 'repos'\n join cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n WHERE\n t.name in (${team})\n and pr.merged_date is not null\n and ppm.pr_cycle_time is not null\n and $__timeFilter(cdc.finished_date)\n),\n_median_change_lead_time_ranks as(\n SELECT\n *,\n percent_rank() over(\n order by\n pr_cycle_time\n ) as ranks\n FROM\n _pr_stats\n),\n_median_change_lead_time as(\n -- use median PR cycle time as the median change lead time\n SELECT\n max(pr_cycle_time) as median_change_lead_time\n FROM\n _median_change_lead_time_ranks\n WHERE\n ranks <= 0.5\n),\n_metric_change_lead_time as (\n SELECT\n 'Lead time for changes' as metric,\n CASE\n WHEN ('$dora_report') = '2023' THEN CASE\n WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n WHEN ('$dora_report') = '2021' THEN CASE\n WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n ELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n END\n ELSE 'Invalid dora report'\n END AS value\n FROM\n _median_change_lead_time\n),\n-- Metric 3: change failure rate\n_deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.05 THEN \"0-5%(elite)\" WHEN change_failure_rate <= 0.10 THEN \"5%-10%(high)\" WHEN change_failure_rate <= 0.15 THEN \"10%-15%(medium)\" WHEN change_failure_rate > 0.15 THEN \"> 15%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN change_failure_rate <= 0.15 THEN \"0-15%(elite)\" WHEN change_failure_rate <= 0.20 THEN \"16%-20%(high)\" WHEN change_failure_rate <= 0.30 THEN \"21%-30%(medium)\" WHEN change_failure_rate > 0.30 THEN \"> 30%(low)\" ELSE \"N/A. Please check if you have collected deployments/incidents.\" END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\" WHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\" WHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", "refId": "A", "sql": { "columns": [ @@ -325,7 +325,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_7_PLACEHOLDER deployment days per week(high)STRING_LITERAL_8_PLACEHOLDER deployment days per month(medium)STRING_LITERAL_9_PLACEHOLDER deployment days per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDER deployment days per week(elite)STRING_LITERAL_13_PLACEHOLDER deployment days per month(high)STRING_LITERAL_14_PLACEHOLDER deployment days per six months(medium)STRING_LITERAL_15_PLACEHOLDER deployment days per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_week >= 1 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(high)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(medium)') WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(low)') ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(high)') WHEN median_number_of_deployment_days_per_six_months >= 1 THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(medium)') WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(low)') ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -462,7 +462,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "sql": { "columns": [ @@ -579,7 +579,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "-- Metric 4: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERInvalid dora reportSTRING_LITERAL_11_PLACEHOLDER", + "rawSql": "/* Metric 4: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.10 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.20 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", "refId": "A", "sql": { "columns": [ @@ -697,7 +697,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected deployments or incidents.\" END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "sql": { "columns": [ @@ -804,7 +804,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -909,7 +909,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hour' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -1033,7 +1033,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "-- Metric 4: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER", + "rawSql": "/* Metric 4: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -1143,7 +1143,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/DORADebug.json b/grafana/dashboards/postgresql/DORADebug.json index 1b720a795ca..6f4ecc50741 100644 --- a/grafana/dashboards/postgresql/DORADebug.json +++ b/grafana/dashboards/postgresql/DORADebug.json @@ -320,7 +320,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER$dora_reportSTRING_LITERAL_5_PLACEHOLDER2023STRING_LITERAL_6_PLACEHOLDEROn-demand(elite)STRING_LITERAL_7_PLACEHOLDERBetween once per day and once per week(high)STRING_LITERAL_8_PLACEHOLDERBetween once per week and once per month(medium)STRING_LITERAL_9_PLACEHOLDERFewer than once per month(low)STRING_LITERAL_10_PLACEHOLDER$dora_reportSTRING_LITERAL_11_PLACEHOLDER2021STRING_LITERAL_12_PLACEHOLDEROn-demand(elite)STRING_LITERAL_13_PLACEHOLDERBetween once per day and once per month(high)STRING_LITERAL_14_PLACEHOLDERBetween once per month and once every 6 months(medium)STRING_LITERAL_15_PLACEHOLDERFewer than once per six months(low)STRING_LITERAL_16_PLACEHOLDERInvalid dora reportSTRING_LITERAL_17_PLACEHOLDERDeployment FrequencySTRING_LITERAL_18_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -557,7 +557,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Number of deployments per month\nwith _deployments as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDER%y/%mSTRING_LITERAL_1_PLACEHOLDERcicd_scopesSTRING_LITERAL_2_PLACEHOLDERSUCCESSSTRING_LITERAL_3_PLACEHOLDERPRODUCTIONSTRING_LITERAL_4_PLACEHOLDERDeployment CountSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -924,7 +924,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(day, '%y/%m') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(day, 'YY/MM') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1189,7 +1189,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\" WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\" WHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\" WHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\" ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\" WHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\" WHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\" WHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\" ELSE \"N/A. Please check if you have collected deployments/pull_requests.\" END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -1466,7 +1466,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, '%y/%m') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS 'Median Change Lead Time In Hour' FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -1574,7 +1574,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS 'No. of merged PRs in table.pull_requests' FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS \"No. of merged PRs in table.pull_requests\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL", "refId": "A", "select": [ [ @@ -1604,7 +1604,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'No. of PRs in table.project_pr_metrics' FROM project_pr_metrics WHERE project_name IN ($project)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"No. of PRs in table.project_pr_metrics\" FROM project_pr_metrics WHERE project_name IN ($project)", "refId": "B", "select": [ [ @@ -2049,7 +2049,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _pr_commit_ranks AS (SELECT pr.id, pr.created_date AS pr_created_date, prc.commit_sha, prc.commit_authored_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.commit_authored_date ASC NULLS FIRST) AS commit_rank FROM pull_requests AS pr LEFT JOIN pull_request_commits AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id') SELECT id, CEIL(CAST(EXTRACT(EPOCH FROM (pr_created_date - commit_authored_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR coding time from PRs and commits' /* commit_sha as first_commit_sha, */ /* commit_authored_date as first_commit_authored_date, */ FROM _pr_commit_ranks WHERE commit_rank = 1", + "rawSql": "WITH _pr_commit_ranks AS (SELECT pr.id, pr.created_date AS pr_created_date, prc.commit_sha, prc.commit_authored_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.commit_authored_date ASC NULLS FIRST) AS commit_rank FROM pull_requests AS pr LEFT JOIN pull_request_commits AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id') SELECT id, CEIL(CAST(EXTRACT(EPOCH FROM (pr_created_date - commit_authored_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR coding time from PRs and commits\" /* commit_sha as first_commit_sha, */ /* commit_authored_date as first_commit_authored_date, */ FROM _pr_commit_ranks WHERE commit_rank = 1", "refId": "A", "select": [ [ @@ -2079,7 +2079,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_coding_time AS 'PR coding time from project_pr_metrics' /* first_commit_sha, */ FROM project_pr_metrics WHERE id = '$pr_id'", + "rawSql": "SELECT id, pr_coding_time AS \"PR coding time from project_pr_metrics\" /* first_commit_sha, */ FROM project_pr_metrics WHERE id = '$pr_id'", "refId": "B", "select": [ [ @@ -2157,7 +2157,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.created_date AS pr_created_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, pr_created_date, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (review_created_date - pr_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR pickup time from pr_comments' FROM _pr_comment_ranks WHERE comment_rank = 1", + "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.created_date AS pr_created_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, pr_created_date, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (review_created_date - pr_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR pickup time from pr_comments\" FROM _pr_comment_ranks WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -2187,7 +2187,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_pickup_time AS 'PR pickup time from project_pr_metrics' /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_pickup_time AS \"PR pickup time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", "refId": "B", "select": [ [ @@ -2265,7 +2265,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.merged_date AS pr_merged_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank_asc FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (pr_merged_date - review_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR review time from pr_comments' FROM _pr_comment_ranks WHERE comment_rank_asc = 1", + "rawSql": "WITH _pr_comment_ranks AS (SELECT pr.id AS pr_id, pr.merged_date AS pr_merged_date, prc.id AS review_id, prc.created_date AS review_created_date, ROW_NUMBER() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) AS comment_rank_asc FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE pr.id = '$pr_id' AND prc.account_id <> pr.author_id) SELECT pr_id, review_id AS first_review_id, review_created_date AS first_review_time, CEIL(CAST(EXTRACT(EPOCH FROM (pr_merged_date - review_created_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR review time from pr_comments\" FROM _pr_comment_ranks WHERE comment_rank_asc = 1", "refId": "A", "select": [ [ @@ -2295,7 +2295,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_review_time AS 'PR review time from project_pr_metrics' /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_review_time AS \"PR review time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", "refId": "B", "select": [ [ @@ -2373,7 +2373,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS 'PR deploy time from cicd_deployment_commits' FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", + "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR deploy time from cicd_deployment_commits\" FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2403,7 +2403,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_deploy_time AS 'PR deploy time from project_pr_metrics' FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_deploy_time AS \"PR deploy time from project_pr_metrics\" FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", "refId": "B", "select": [ [ @@ -2481,7 +2481,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS 'PR cycle time from lower-level metrics', ppm.\"pr_cycle_time\" AS 'PR cycle time from project_pr_metrics' FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND pr.id = '$pr_id'", + "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS \"PR cycle time from lower-level metrics\", ppm.\"pr_cycle_time\" AS \"PR cycle time from project_pr_metrics\" FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND pr.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2617,7 +2617,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(cdc.finished_date, '%y/%m') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(cdc.finished_date, 'YY/MM') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", "refId": "A", "select": [ [ @@ -2930,7 +2930,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE \"No data\" END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\" WHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\" WHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\" WHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\" ELSE \"N/A. Please check if you have collected incidents.\" END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -3210,7 +3210,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.created_date, '%y/%m') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS '${title_value} In Hours' FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.created_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ @@ -3348,7 +3348,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDERNo AllSTRING_LITERAL_4_PLACEHOLDERNo IncidentsSTRING_LITERAL_5_PLACEHOLDERNo DeploymentsSTRING_LITERAL_6_PLACEHOLDER$dora_reportSTRING_LITERAL_7_PLACEHOLDER2023STRING_LITERAL_8_PLACEHOLDER$dora_reportSTRING_LITERAL_9_PLACEHOLDER2021STRING_LITERAL_10_PLACEHOLDERInvalid dora reportSTRING_LITERAL_11_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN i.id = NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", "refId": "A", "select": [ [ @@ -3711,7 +3711,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%y/%mSTRING_LITERAL_4_PLACEHOLDERChange Failure RateSTRING_LITERAL_5_PLACEHOLDER%Y-%m-01STRING_LITERAL_6_PLACEHOLDER%Y-%m-01STRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json b/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json index 23a20909db4..e2a6d5c6489 100644 --- a/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json +++ b/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) AS \"change_failure_rate\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -365,7 +365,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id AS \"deployment_id\", d.deployment_finished_date, pim.id AS incident_id, i.title, i.url, i.url AS \"metric_hidden\", i.created_date /* i.resolution_date, */ FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN issues AS i ON pim.id = i.id WHERE NOT pim.id IS NULL ORDER BY 2 NULLS FIRST) SELECT * FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n -- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT SUM(has_incident) AS \"incident count\", COUNT(deployment_id) AS \"deployment count\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -627,7 +627,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project) AND $__timeFilter(i.created_date)) SELECT finished_date AS 'Time (Ascending)', deployment_id AS 'Entity ID', 'DEPLOYMENT' AS 'Entity Type (Deployment/Incident)' FROM _deployments UNION SELECT created_date AS 'Time (Ascending)', issue_id AS 'Entity ID', 'INCIDENT' AS 'Entity Type (Deployment/Incident)' FROM _incidents ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project) AND $__timeFilter(i.created_date)) SELECT finished_date AS \"Time (Ascending)\", deployment_id AS \"Entity ID\", 'DEPLOYMENT' AS \"Entity Type (Deployment/Incident)\" FROM _deployments UNION SELECT created_date AS \"Time (Ascending)\", issue_id AS \"Entity ID\", 'INCIDENT' AS \"Entity Type (Deployment/Incident)\" FROM _incidents ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json b/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json index cabce1b2e98..4b5b50c6af3 100644 --- a/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json +++ b/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json @@ -503,7 +503,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%m/%dSTRING_LITERAL_5_PLACEHOLDER - STRING_LITERAL_6_PLACEHOLDER%m/%dSTRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), calendar_weeks AS (SELECT DISTINCT CAST(CAST(day AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM last_few_calendar_months ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '6 DAY', '%m/%d')) AS week, days_deployed FROM calendar_weeks AS cw LEFT JOIN _days_weekly_deploy AS b ON cw.start_of_week = b.week", "refId": "A", "select": [ [ @@ -633,7 +633,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%y/%mSTRING_LITERAL_5_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(month, 'YY/MM') AS month, days_deployed FROM _days_monthly_deploy", "refId": "A", "select": [ [ @@ -762,7 +762,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER%y/%mSTRING_LITERAL_5_PLACEHOLDER ~ STRING_LITERAL_6_PLACEHOLDER%y/%mSTRING_LITERAL_7_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT CONCAT(TO_CHAR(month - INTERVAL '5 MONTH', 'YY/MM'), ' ~ ', TO_CHAR(month, 'YY/MM')) AS month_range, days_deployed_per_six_months AS days_deployed FROM _days_six_months_deploy ORDER BY month NULLS FIRST", "refId": "A", "select": [ [ @@ -925,7 +925,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_week AS \"Median weekly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\", */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1063,7 +1063,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_month AS \"Median monthly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1201,7 +1201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30\n\t\t\tUNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days as(\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last oneSTRING_LITERAL_0_PLACEHOLDERcicd_scopesSTRING_LITERAL_1_PLACEHOLDERSUCCESSSTRING_LITERAL_2_PLACEHOLDERPRODUCTIONSTRING_LITERAL_3_PLACEHOLDER%Y-%mSTRING_LITERAL_4_PLACEHOLDER", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_six_months AS \"Median semi-annual deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json index acd10b5b77d..44be8b405a1 100644 --- a/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json +++ b/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json @@ -121,7 +121,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = \"No deployments and incidents\" THEN \"N/A. Please check if you have collected deployments and incidents.\" WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\" WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\" WHEN NOT median_recovery_time IS NULL THEN CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) ELSE \"No data\" END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN NOT median_recovery_time IS NULL THEN CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) ELSE 'No data' END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", "refId": "D", "sql": { "columns": [ @@ -409,7 +409,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, '%y/%m') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", "refId": "D", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json b/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json index 2337bf19517..8c8aca0bbe2 100644 --- a/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json +++ b/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json @@ -132,7 +132,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS 'PR Cycle Time(h)' FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -240,7 +240,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS 'Coding Time(h)' FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS \"Coding Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -379,7 +379,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS 'Pickup Time(h)' FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS 'Review Time(h)' FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS \"Review Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -657,7 +657,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS 'PR Deploy Time(h)' FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json b/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json index a8efd2838f0..4cfe4d08136 100644 --- a/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json +++ b/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json @@ -111,7 +111,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS 'Assignee', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", "refId": "A", "select": [ [ @@ -182,7 +182,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS 'Assignee', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json b/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json index 3ea056c1ed5..2b60aa04d6b 100644 --- a/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json +++ b/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json @@ -112,7 +112,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -187,7 +187,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS 'Author Name', COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DemoDetailedBugInfo.json b/grafana/dashboards/postgresql/DemoDetailedBugInfo.json index 50479310dd4..303e487028c 100644 --- a/grafana/dashboards/postgresql/DemoDetailedBugInfo.json +++ b/grafana/dashboards/postgresql/DemoDetailedBugInfo.json @@ -114,7 +114,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1 ORDER BY 1 DESC NULLS LAST) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count AS 'Bug Count over Month' FROM bugs ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1 ORDER BY 1 DESC NULLS LAST) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count AS \"Bug Count over Month\" FROM bugs ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json b/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json index 864d7a57d46..acfdbc4a06c 100644 --- a/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json +++ b/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json @@ -108,7 +108,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time_days FROM issues AS i WHERE type = 'REQUIREMENT' AND $__timeFilter(resolution_date) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, lead_time_days AS 'Average Requirement Lead Time (day)' FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time_days FROM issues AS i WHERE type = 'REQUIREMENT' AND $__timeFilter(resolution_date) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, lead_time_days AS \"Average Requirement Lead Time (day)\" FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json b/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json index 598a0318003..bcc358ea604 100644 --- a/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json +++ b/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json @@ -109,7 +109,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> 0) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count_per_1k_loc AS 'Bug Count per 1000 Lines of Code' FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", + "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> 0) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count_per_1k_loc AS \"Bug Count per 1000 Lines of Code\" FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/DeveloperProductivityHours.json b/grafana/dashboards/postgresql/DeveloperProductivityHours.json index 37253acc4f1..8a044ddff26 100644 --- a/grafana/dashboards/postgresql/DeveloperProductivityHours.json +++ b/grafana/dashboards/postgresql/DeveloperProductivityHours.json @@ -66,7 +66,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT LPAD(CAST(hr AS TEXT), 2, '0') AS 'Hour', SUM(chat) AS 'Chat Events', SUM(comp) AS 'Completion Events' FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hr, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hr, 0 AS chat, COUNT(*) AS comp FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS t GROUP BY hr ORDER BY hr NULLS FIRST", + "rawSql": "SELECT LPAD(CAST(hr AS TEXT), 2, '0') AS \"Hour\", SUM(chat) AS \"Chat Events\", SUM(comp) AS \"Completion Events\" FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hr, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hr, 0 AS chat, COUNT(*) AS comp FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS t GROUP BY hr ORDER BY hr NULLS FIRST", "refId": "A" } ], @@ -125,7 +125,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS 'Hour', ROUND(AVG(prompt_length)) AS 'Avg Prompt Length', ROUND(AVG(response_length)) AS 'Avg Response Length' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", + "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS \"Hour\", ROUND(AVG(prompt_length)) AS \"Avg Prompt Length\", ROUND(AVG(response_length)) AS \"Avg Response Length\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", "refId": "A" } ], @@ -180,7 +180,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS 'Hour', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode', SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS 'Plain Chat' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", + "rawSql": "SELECT LPAD(CAST(EXTRACT(HOUR FROM timestamp) AS TEXT), 2, '0') AS \"Hour\", SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS \"Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS \"Spec Mode\", SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS \"Plain Chat\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) ORDER BY EXTRACT(HOUR FROM timestamp) NULLS FIRST", "refId": "A" } ], @@ -235,7 +235,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) WHEN 1 THEN 'Sun' WHEN 2 THEN 'Mon' WHEN 3 THEN 'Tue' WHEN 4 THEN 'Wed' WHEN 5 THEN 'Thu' WHEN 6 THEN 'Fri' WHEN 7 THEN 'Sat' END AS 'Day', SUM(chat) AS 'Chat Events', SUM(comp) AS 'Completions' FROM (SELECT timestamp, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY timestamp UNION ALL SELECT timestamp, 0, COUNT(*) FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY timestamp) AS t GROUP BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) ORDER BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) NULLS FIRST", + "rawSql": "SELECT CASE EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) WHEN 1 THEN 'Sun' WHEN 2 THEN 'Mon' WHEN 3 THEN 'Tue' WHEN 4 THEN 'Wed' WHEN 5 THEN 'Thu' WHEN 6 THEN 'Fri' WHEN 7 THEN 'Sat' END AS \"Day\", SUM(chat) AS \"Chat Events\", SUM(comp) AS \"Completions\" FROM (SELECT timestamp, COUNT(*) AS chat, 0 AS comp FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY timestamp UNION ALL SELECT timestamp, 0, COUNT(*) FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY timestamp) AS t GROUP BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) ORDER BY EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) NULLS FIRST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/EngineeringOverview.json b/grafana/dashboards/postgresql/EngineeringOverview.json index 88e7c89de02..3457a601f8a 100644 --- a/grafana/dashboards/postgresql/EngineeringOverview.json +++ b/grafana/dashboards/postgresql/EngineeringOverview.json @@ -502,7 +502,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = \"DONE\" AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2075,7 +2075,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS 'Bug', COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS 'Strategic', COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS 'Non-Strategic' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS \"Bug\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS \"Strategic\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS \"Non-Strategic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2207,7 +2207,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.priority AS 'Priority', AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS 'Average Age' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY i.priority", + "rawSql": "SELECT i.priority AS \"Priority\", AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS \"Average Age\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY i.priority", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json index 47b475a46fd..263c085e125 100644 --- a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json +++ b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json @@ -360,7 +360,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS 'Issues Opened', COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS 'Issues Completed' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS \"Issues Opened\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Issues Completed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -483,7 +483,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS 'Story Points Completed' FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS \"Story Points Completed\" FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -745,7 +745,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS 'P0/P1 Bugs' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS \"P0/P1 Bugs\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -868,7 +868,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS 'PR Merged Size' FROM _pr_commits_data GROUP BY 1", + "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS \"PR Merged Size\" FROM _pr_commits_data GROUP BY 1", "refId": "A", "select": [ [ @@ -1149,7 +1149,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(cycle_time) AS 'PR Cycle Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1285,7 +1285,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(coding_time) AS 'Coding Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(coding_time) AS \"Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1452,7 +1452,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(pickup_time) AS 'Pickup Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1619,7 +1619,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(review_time) AS 'Review Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(review_time) AS \"Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1786,7 +1786,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(deploy_time) AS 'PR Deploy Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json index 7588c90d806..6f0a6a6da0a 100644 --- a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json +++ b/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json @@ -866,7 +866,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS 'Team1: Issues Completed' /* count(i.id) as 'Issues Opened', */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS 'Team2: Issues Completed' FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: Issues Completed\" /* count(i.id) AS \"Issues Opened\", */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: Issues Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1037,7 +1037,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: Issues Completed per Member', CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: Issues Completed per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: Issues Completed per Member\", CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: Issues Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1234,7 +1234,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS 'Team1: Story Points Completed' /* count(i.id) as 'Issues Opened', */, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS 'Team2: Story Points Completed' FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS \"Team1: Story Points Completed\" /* count(i.id) AS \"Issues Opened\", */, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS \"Team2: Story Points Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1405,7 +1405,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: Story Points Completed per Member', CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: Story Points Completed per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: Story Points Completed per Member\", CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: Story Points Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1970,7 +1970,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS 'Team1: P0/P1 Bugs', COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS 'Team2: P0/P1 Bugs' FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: P0/P1 Bugs\", COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: P0/P1 Bugs\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2141,7 +2141,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS 'Team1: P0/P1 Bugs per Member', CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS 'Team2: P0/P1 Bugs per Member', CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: P0/P1 Bugs per Member\", CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: P0/P1 Bugs per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2335,7 +2335,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN cycle_time END) AS 'Team1: Avg Cycle Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN cycle_time END) AS 'Team2: Avg Cycle Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN cycle_time END) AS \"Team1: Avg Cycle Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN cycle_time END) AS \"Team2: Avg Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2502,7 +2502,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN coding_time END) AS 'Team1: Avg Coding Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN coding_time END) AS 'Team2: Avg Coding Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN coding_time END) AS \"Team1: Avg Coding Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN coding_time END) AS \"Team2: Avg Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2669,7 +2669,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN pickup_time END) AS 'Team1: Avg Pickup Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN pickup_time END) AS 'Team2: Avg Pickup Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN pickup_time END) AS \"Team1: Avg Pickup Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN pickup_time END) AS \"Team2: Avg Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2836,7 +2836,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN review_time END) AS 'Team1: Avg Review Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN review_time END) AS 'Team2: Avg Review Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN review_time END) AS \"Team1: Avg Review Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN review_time END) AS \"Team2: Avg Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -3003,7 +3003,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN deploy_time END) AS 'Team1: Avg Deploy Time(h)', AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN deploy_time END) AS 'Team2: Avg Deploy Time(h)' FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN deploy_time END) AS \"Team1: Avg Deploy Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN deploy_time END) AS \"Team2: Avg Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/GitHub.json b/grafana/dashboards/postgresql/GitHub.json index 8db63259901..dbf7222a3eb 100644 --- a/grafana/dashboards/postgresql/GitHub.json +++ b/grafana/dashboards/postgresql/GitHub.json @@ -415,7 +415,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\"", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -659,7 +659,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\"", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -781,7 +781,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = \"DONE\" AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1921,7 +1921,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -2056,7 +2056,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", "refId": "A", "select": [ [ @@ -2626,7 +2626,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2733,7 +2733,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2901,7 +2901,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_workflow_run_count, failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", + "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_workflow_run_count, failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3088,7 +3088,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -3245,7 +3245,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3346,7 +3346,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -3501,7 +3501,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%github%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json b/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json index 19cfb137c71..699d1739afc 100644 --- a/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json +++ b/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json @@ -153,7 +153,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, active_users, total_seats, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, ROUND(AVG(adoption_pct), 1) AS adoption_pct, ROUND(AVG(active_users), 0) AS avg_active_users, ROUND(AVG(total_seats), 0) AS avg_total_seats FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, adoption_pct AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, active_users, total_seats, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, ROUND(AVG(adoption_pct), 1) AS adoption_pct, ROUND(AVG(active_users), 0) AS avg_active_users, ROUND(AVG(total_seats), 0) AS avg_total_seats FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, adoption_pct AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "A" } ], @@ -602,14 +602,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "Adoption" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(avg_cycle_time_hours, 1) AS 'PR Cycle Time (hrs)' FROM _pr_metrics_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(avg_cycle_time_hours, 1) AS \"PR Cycle Time (hrs)\" FROM _pr_metrics_weekly ORDER BY week_start NULLS FIRST", "refId": "PRTime" } ], @@ -1071,7 +1071,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', COALESCE(dw.deploy_count, 0) AS 'Deployments' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", COALESCE(dw.deploy_count, 0) AS \"Deployments\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -1144,7 +1144,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(COALESCE(dw.deploy_count, 0)), 1) AS 'Deploys/Week' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(COALESCE(dw.deploy_count, 0)), 1) AS \"Deploys/Week\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -1369,7 +1369,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, adoption_pct, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, AVG(CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0)) AS cycle_time_hrs FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND NOT pr_merged_date IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _cfr_weekly AS (SELECT week_start, ROUND(CAST(failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(deploy_count, 0), 0), 1) AS cfr_pct FROM _deploy_weekly), _mttr_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _tier_metrics AS (SELECT at.tier, ROUND(AVG(pw.cycle_time_hrs), 1) AS pr_cycle_time, ROUND(AVG(dw.deploy_count), 1) AS deploy_freq, ROUND(AVG(cfr.cfr_pct), 1) AS cfr_pct, ROUND(AVG(mttr.mttr_hours), 1) AS mttr_hours FROM _adoption_tiers AS at LEFT JOIN _pr_weekly AS pw ON at.week_start = pw.week_start LEFT JOIN _deploy_weekly AS dw ON at.week_start = dw.week_start LEFT JOIN _cfr_weekly AS cfr ON at.week_start = cfr.week_start LEFT JOIN _mttr_weekly AS mttr ON at.week_start = mttr.week_start GROUP BY at.tier) SELECT tier AS 'Adoption Tier', COALESCE(pr_cycle_time, 0) AS 'PR Cycle Time (hrs)', COALESCE(deploy_freq, 0) AS 'Deploys/Week', COALESCE(cfr_pct, 0) AS 'CFR %', COALESCE(mttr_hours, 0) AS 'MTTR (hours)' FROM _tier_metrics ORDER BY FIELD(tier, '<25%', '25-50%', '50-75%', '>75%') NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, adoption_pct, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, AVG(CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0)) AS cycle_time_hrs FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND NOT pr_merged_date IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _cfr_weekly AS (SELECT week_start, ROUND(CAST(failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(deploy_count, 0), 0), 1) AS cfr_pct FROM _deploy_weekly), _mttr_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _tier_metrics AS (SELECT at.tier, ROUND(AVG(pw.cycle_time_hrs), 1) AS pr_cycle_time, ROUND(AVG(dw.deploy_count), 1) AS deploy_freq, ROUND(AVG(cfr.cfr_pct), 1) AS cfr_pct, ROUND(AVG(mttr.mttr_hours), 1) AS mttr_hours FROM _adoption_tiers AS at LEFT JOIN _pr_weekly AS pw ON at.week_start = pw.week_start LEFT JOIN _deploy_weekly AS dw ON at.week_start = dw.week_start LEFT JOIN _cfr_weekly AS cfr ON at.week_start = cfr.week_start LEFT JOIN _mttr_weekly AS mttr ON at.week_start = mttr.week_start GROUP BY at.tier) SELECT tier AS \"Adoption Tier\", COALESCE(pr_cycle_time, 0) AS \"PR Cycle Time (hrs)\", COALESCE(deploy_freq, 0) AS \"Deploys/Week\", COALESCE(cfr_pct, 0) AS \"CFR %\", COALESCE(mttr_hours, 0) AS \"MTTR (hours)\" FROM _tier_metrics ORDER BY FIELD(tier, '<25%', '25-50%', '50-75%', '>75%') NULLS FIRST", "refId": "A" } ], @@ -1524,7 +1524,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', ROUND(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0), 1) AS 'CFR %' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -1601,7 +1601,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)), 1) AS 'CFR %' FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -1908,14 +1908,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS 'Adoption %' FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "Adoption" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(mttr_hours, 1) AS 'MTTR (hours)' FROM _incidents_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(mttr_hours, 1) AS \"MTTR (hours)\" FROM _incidents_weekly ORDER BY week_start NULLS FIRST", "refId": "MTTR" } ], @@ -1992,7 +1992,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(iw.mttr_hours), 1) AS 'MTTR (hours)' FROM _adoption_weekly AS aw LEFT JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start WHERE NOT iw.mttr_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(iw.mttr_hours), 1) AS \"MTTR (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start WHERE NOT iw.mttr_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -2234,7 +2234,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS 'Adoption Tier', ROUND(AVG(prw.avg_review_time_hours), 1) AS 'Review Time (hours)' FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start WHERE NOT prw.avg_review_time_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(prw.avg_review_time_hours), 1) AS \"Review Time (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start WHERE NOT prw.avg_review_time_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -2376,7 +2376,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS 'Adoption %', ROUND(prw.avg_review_time_hours, 1) AS 'Review Time (h)' FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(prw.avg_review_time_hours, 1) AS \"Review Time (h)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -2607,7 +2607,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly) SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS 'Bugs/File' FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly) SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS \"Bugs/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], @@ -2673,7 +2673,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS 'Code Smells/File' FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS \"Code Smells/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json b/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json index 43ed6fb03c9..75bc25c3fe4 100644 --- a/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json +++ b/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json @@ -331,7 +331,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -487,7 +487,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -573,7 +573,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, \" \", CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -1696,7 +1696,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN \">=avg_bug_age\" ELSE \" 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", + "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN '>=avg_bug_age' ELSE ' 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", "refId": "A", "select": [ [ @@ -2517,7 +2517,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "", + "rawSql": "/* Get each contributor's work in bugfixing in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _author_commits AS (SELECT c.author_name, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND SUBSTRING_INDEX(rcf.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1), _author_commits_running_total AS (SELECT *, SUM(commit_count) OVER (ORDER BY commit_count DESC NULLS LAST) AS running_total FROM _author_commits), _percentile AS (SELECT author_name, commit_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(commit_count) OVER (), 0) AS cumulative_percentage FROM _author_commits_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN author_name ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"contributors who contributed 80% of dev_eq\" FROM _percentile", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/Gitlab.json b/grafana/dashboards/postgresql/Gitlab.json index 7a8f48cc3e3..0a095be3a80 100644 --- a/grafana/dashboards/postgresql/Gitlab.json +++ b/grafana/dashboards/postgresql/Gitlab.json @@ -439,7 +439,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -566,7 +566,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", "refId": "A", "select": [ [ @@ -832,7 +832,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -1312,7 +1312,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1419,7 +1419,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1587,7 +1587,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1774,7 +1774,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1915,7 +1915,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2016,7 +2016,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2171,7 +2171,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%gitlab%\" AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/Jenkins.json b/grafana/dashboards/postgresql/Jenkins.json index 466eef95e0f..a6e44d916a2 100644 --- a/grafana/dashboards/postgresql/Jenkins.json +++ b/grafana/dashboards/postgresql/Jenkins.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -510,7 +510,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -631,7 +631,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -788,7 +788,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -955,7 +955,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1091,7 +1091,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE \"%jenkins%\" AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/KiroCreditsDORA.json b/grafana/dashboards/postgresql/KiroCreditsDORA.json index 919483048f9..78a0214a29c 100644 --- a/grafana/dashboards/postgresql/KiroCreditsDORA.json +++ b/grafana/dashboards/postgresql/KiroCreditsDORA.json @@ -145,7 +145,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_hours FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, p.avg_cycle_hours AS y FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS 'r' FROM _stats AS s", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_hours FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, p.avg_cycle_hours AS y FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", "refId": "A" } ], @@ -197,7 +197,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(credits_used) AS 'Credits Used' FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", + "rawSql": "SELECT SUM(credits_used) AS \"Credits Used\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date)", "refId": "A" } ], @@ -250,7 +250,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _ranked AS (SELECT CAST(pr_cycle_time AS NUMERIC) / NULLIF(60.0, 0) AS ct, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS prank FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date)) SELECT ROUND(MAX(ct), 1) AS 'Median Cycle Time' FROM _ranked WHERE prank <= 0.5", + "rawSql": "WITH _ranked AS (SELECT CAST(pr_cycle_time AS NUMERIC) / NULLIF(60.0, 0) AS ct, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS prank FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date)) SELECT ROUND(MAX(ct), 1) AS \"Median Cycle Time\" FROM _ranked WHERE prank <= 0.5", "refId": "A" } ], @@ -301,7 +301,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_ct FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits, p.avg_ct FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _med AS (SELECT weekly_credits AS med FROM _joined ORDER BY weekly_credits NULLS FIRST LIMIT 1 OFFSET (SELECT FLOOR(CAST(COUNT(*) AS NUMERIC) / NULLIF(2, 0)) FROM _joined)) SELECT CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END AS 'Tier', ROUND(AVG(j.avg_ct), 1) AS 'Avg Cycle Time' FROM _joined AS j, _med AS m GROUP BY CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_ct FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits, p.avg_ct FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _med AS (SELECT weekly_credits AS med FROM _joined ORDER BY weekly_credits NULLS FIRST LIMIT 1 OFFSET (SELECT FLOOR(CAST(COUNT(*) AS NUMERIC) / NULLIF(2, 0)) FROM _joined)) SELECT CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END AS \"Tier\", ROUND(AVG(j.avg_ct), 1) AS \"Avg Cycle Time\" FROM _joined AS j, _med AS m GROUP BY CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END", "refId": "A" } ], @@ -377,7 +377,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS 'Credits Used', COUNT(DISTINCT user_id) AS 'Active Users' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits Used\", COUNT(DISTINCT user_id) AS \"Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -440,7 +440,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS time, ROUND(CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS 'Avg Cycle Time (hrs)', COUNT(*) AS 'PRs Merged' FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS time, ROUND(CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS \"Avg Cycle Time (hrs)\", COUNT(*) AS \"PRs Merged\" FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -515,7 +515,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, d.deploys AS y FROM _kiro_weekly AS k INNER JOIN _deploy_weekly AS d ON k.week_start = d.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS 'r' FROM _stats AS s", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, d.deploys AS y FROM _kiro_weekly AS k INNER JOIN _deploy_weekly AS d ON k.week_start = d.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", "refId": "A" } ], @@ -578,7 +578,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(credits) AS 'Credits Used', SUM(deploys) AS 'Deployments' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS deploys FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(deploys) AS \"Deployments\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS deploys FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -654,7 +654,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(credits) AS 'Credits Used', SUM(cfr) AS 'Change Failure Rate' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS cfr FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, CAST(SUM(CASE WHEN NOT i.id IS NULL THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT d.deployment_id), 0) AS cfr FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY cdc.cicd_deployment_id) AS d LEFT JOIN project_incident_deployment_relationships AS pidr ON d.deployment_id = pidr.deployment_id LEFT JOIN incidents AS i ON pidr.id = i.id GROUP BY CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(cfr) AS \"Change Failure Rate\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS cfr FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, CAST(SUM(CASE WHEN NOT i.id IS NULL THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT d.deployment_id), 0) AS cfr FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY cdc.cicd_deployment_id) AS d LEFT JOIN project_incident_deployment_relationships AS pidr ON d.deployment_id = pidr.deployment_id LEFT JOIN incidents AS i ON pidr.id = i.id GROUP BY CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/LanguageAIHeatmap.json b/grafana/dashboards/postgresql/LanguageAIHeatmap.json index 467e0fa2d70..20812080377 100644 --- a/grafana/dashboards/postgresql/LanguageAIHeatmap.json +++ b/grafana/dashboards/postgresql/LanguageAIHeatmap.json @@ -66,7 +66,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS 'Language', COUNT(*) AS 'Requests', ROUND(AVG(completions_count), 2) AS 'Avg Completions', ROUND(AVG(left_context_length)) AS 'Avg Left Context', ROUND(AVG(right_context_length)) AS 'Avg Right Context', ROUND(AVG(left_context_length + right_context_length)) AS 'Avg Total Context', COUNT(DISTINCT user_id) AS 'Users' FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"Language\", COUNT(*) AS \"Requests\", ROUND(AVG(completions_count), 2) AS \"Avg Completions\", ROUND(AVG(left_context_length)) AS \"Avg Left Context\", ROUND(AVG(right_context_length)) AS \"Avg Right Context\", ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\", COUNT(DISTINCT user_id) AS \"Users\" FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -186,7 +186,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file)' ELSE active_file_extension END AS 'File Type', COUNT(*) AS 'Chat Events' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/MultiAIComparison.json b/grafana/dashboards/postgresql/MultiAIComparison.json index 27c800329c8..bb0bd28b023 100644 --- a/grafana/dashboards/postgresql/MultiAIComparison.json +++ b/grafana/dashboards/postgresql/MultiAIComparison.json @@ -134,14 +134,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT user_id) AS 'Kiro Active Users' FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT user_id) AS \"Kiro Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, MAX(daily_active_users) AS 'Copilot Active Users' FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, MAX(daily_active_users) AS \"Copilot Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "B" } ], @@ -217,7 +217,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_suggestions_count) AS 'Kiro: Suggestions', SUM(inline_acceptance_count) AS 'Kiro: Accepted' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_suggestions_count) AS \"Kiro: Suggestions\", SUM(inline_acceptance_count) AS \"Kiro: Accepted\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -280,7 +280,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, SUM(code_generation_activity_count) AS 'Copilot: Suggestions', SUM(code_acceptance_activity_count) AS 'Copilot: Accepted' FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, SUM(code_generation_activity_count) AS \"Copilot: Suggestions\", SUM(code_acceptance_activity_count) AS \"Copilot: Accepted\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -356,7 +356,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(kiro_loc) AS 'Kiro LOC Accepted', SUM(copilot_loc) AS 'Copilot LOC Added' FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_ai_code_lines + chat_ai_code_lines) AS kiro_loc, 0 AS copilot_loc FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, 0 AS kiro_loc, SUM(loc_added_sum) AS copilot_loc FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(kiro_loc) AS \"Kiro LOC Accepted\", SUM(copilot_loc) AS \"Copilot LOC Added\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_ai_code_lines + chat_ai_code_lines) AS kiro_loc, 0 AS copilot_loc FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, 0 AS kiro_loc, SUM(loc_added_sum) AS copilot_loc FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -407,7 +407,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'Kiro' AS Tool, ROUND(CAST(SUM(inline_acceptance_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0), 1) AS 'Acceptance Rate' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Copilot' AS Tool, ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day)", + "rawSql": "SELECT 'Kiro' AS Tool, ROUND(CAST(SUM(inline_acceptance_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0), 1) AS \"Acceptance Rate\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Copilot' AS Tool, ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day)", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/Opsgenie.json b/grafana/dashboards/postgresql/Opsgenie.json index 5c7ec438b5a..7dd6948eebb 100644 --- a/grafana/dashboards/postgresql/Opsgenie.json +++ b/grafana/dashboards/postgresql/Opsgenie.json @@ -1465,7 +1465,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT i.title AS \"Incident Name\", CONCAT('[', STRING_AGG(CONCAT('{\"name\": \"', a.assignee_name, '\"', ', \"type\": \"', r.type, '\"}'), \", \"), ']') AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (SUBSTRING_INDEX(i.id, \":\", -1) = SUBSTRING_INDEX(a.issue_id, \":\", -1)) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", + "rawSql": "SELECT i.title AS \"Incident Name\", CONCAT('[', STRING_AGG(CONCAT('{'name': '', a.assignee_name, ''', ', 'type': '', r.type, ''}'), \", \"), ']') AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (SUBSTRING_INDEX(i.id, \":\", -1) = SUBSTRING_INDEX(a.issue_id, \":\", -1)) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/QDevDORA.json b/grafana/dashboards/postgresql/QDevDORA.json index 7391dad6eb6..1462596bb28 100644 --- a/grafana/dashboards/postgresql/QDevDORA.json +++ b/grafana/dashboards/postgresql/QDevDORA.json @@ -132,7 +132,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Q Dev Users' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"Active Q Dev Users\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -191,7 +191,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(inline_ai_code_lines + chat_ai_code_lines) AS 'AI Accepted Lines' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT SUM(inline_ai_code_lines + chat_ai_code_lines) AS \"AI Accepted Lines\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -258,7 +258,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Acceptance Rate' FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Acceptance Rate\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -316,7 +316,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS 'Deployments' FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", + "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS \"Deployments\" FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", "refId": "A" } ], @@ -383,7 +383,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0), 1) AS 'Lead Time (hours)' FROM _median_ranks WHERE ranks <= 0.5", + "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0), 1) AS \"Lead Time (hours)\" FROM _median_ranks WHERE ranks <= 0.5", "refId": "A" } ], @@ -450,7 +450,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS 'Change Failure Rate' FROM _failure_caused", + "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS \"Change Failure Rate\" FROM _failure_caused", "refId": "A" } ], @@ -601,7 +601,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), lead_time_monthly AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS 'AI Accepted Lines', ROUND(lt.avg_lead_time, 1) AS 'Median Lead Time (hours)' FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), lead_time_monthly AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS \"AI Accepted Lines\", ROUND(lt.avg_lead_time, 1) AS \"Median Lead Time (hours)\" FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -743,7 +743,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), deployment_count AS (SELECT TO_CHAR(MAX(cdc.finished_date), '%Y-%m-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS 'AI Acceptance Rate', dc.deploy_count AS 'Deployment Count' FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), deployment_count AS (SELECT TO_CHAR(MAX(cdc.finished_date), '%Y-%m-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS \"AI Acceptance Rate\", dc.deploy_count AS \"Deployment Count\" FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -885,7 +885,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), cfr_monthly AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS 'AI Generated Tests', cfr.cfr AS 'Change Failure Rate' FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), cfr_monthly AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS \"AI Generated Tests\", cfr.cfr AS \"Change Failure Rate\" FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1023,7 +1023,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT TO_DATE(TO_CHAR(date, '%Y-%m-01'), '%Y-%m-%d') AS time, COUNT(DISTINCT user_id) AS 'Active Users', SUM(code_review_findings_count) AS 'Code Review Findings' FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01') ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(TO_CHAR(date, '%Y-%m-01'), '%Y-%m-%d') AS time, COUNT(DISTINCT user_id) AS \"Active Users\", SUM(code_review_findings_count) AS \"Code Review Findings\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01') ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1175,7 +1175,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(date, '%Y-%m') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m')), dora_metrics AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m')), cfr_metrics AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS 'Month', COALESCE(ai.active_users, 0) AS 'Q Dev Users', COALESCE(ai.ai_lines, 0) AS 'AI Code Lines', ai.acceptance_rate AS 'AI Acceptance Rate', COALESCE(ai.generated_tests, 0) AS 'AI Tests', COALESCE(ai.review_findings, 0) AS 'Review Findings', COALESCE(dm.deployments, 0) AS 'Deployments', ROUND(dm.avg_lead_time, 1) AS 'Lead Time (hours)', cfr.cfr AS 'Change Failure Rate' FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", + "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(date, 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, 'YYYY-MM')), dora_metrics AS (SELECT TO_CHAR(cdc.finished_date, 'YYYY-MM') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, 'YYYY-MM')), cfr_metrics AS (SELECT TO_CHAR(deployment_finished_date, 'YYYY-MM') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, 'YYYY-MM')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS \"Month\", COALESCE(ai.active_users, 0) AS \"Q Dev Users\", COALESCE(ai.ai_lines, 0) AS \"AI Code Lines\", ai.acceptance_rate AS \"AI Acceptance Rate\", COALESCE(ai.generated_tests, 0) AS \"AI Tests\", COALESCE(ai.review_findings, 0) AS \"Review Findings\", COALESCE(dm.deployments, 0) AS \"Deployments\", ROUND(dm.avg_lead_time, 1) AS \"Lead Time (hours)\", cfr.cfr AS \"Change Failure Rate\" FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/SonarQubeCloud.json b/grafana/dashboards/postgresql/SonarQubeCloud.json index 60253103def..5d1f8502a7e 100644 --- a/grafana/dashboards/postgresql/SonarQubeCloud.json +++ b/grafana/dashboards/postgresql/SonarQubeCloud.json @@ -430,7 +430,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Security Hotspots' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -519,7 +519,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS 'Reviewed' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS \"Reviewed\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -724,7 +724,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Code Smells' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -814,7 +814,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS 'Debt' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS \"Debt\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -1405,7 +1405,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS 'Lines of Code', bugs AS 'Bugs', vulnerabilities AS 'Vulnerabilities', code_smells AS 'Code Smells', security_hotspots AS 'Security Hotspots', CONCAT(ROUND(coverage, 2), '%') AS 'Coverage', CONCAT(ROUND(duplicated_lines_density, 2), '%') AS 'Duplications' FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", CONCAT(ROUND(coverage, 2), '%') AS \"Coverage\", CONCAT(ROUND(duplicated_lines_density, 2), '%') AS \"Duplications\" FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/Sonarqube.json b/grafana/dashboards/postgresql/Sonarqube.json index d8c92da978b..478ef6ff6a0 100644 --- a/grafana/dashboards/postgresql/Sonarqube.json +++ b/grafana/dashboards/postgresql/Sonarqube.json @@ -151,7 +151,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Bugs' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'BUG' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Bugs\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'BUG' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -239,7 +239,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Vulnerabilities' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'VULNERABILITY' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Vulnerabilities\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'VULNERABILITY' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -328,7 +328,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Security Hotspots' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -416,7 +416,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS 'Reviewed' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS \"Reviewed\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -619,7 +619,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS 'Debt' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS \"Debt\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -708,7 +708,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS 'Code Smells' FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", "refId": "A", "sql": { "columns": [ @@ -1000,7 +1000,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS 'Lines of Code', bugs AS 'Bugs', vulnerabilities AS 'Vulnerabilities', code_smells AS 'Code Smells', security_hotspots AS 'Security Hotspots', CONCAT(ROUND(coverage, 2), '%') AS 'Coverage', CONCAT(ROUND(duplicated_lines_density, 2), '%') AS 'Duplications' FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", CONCAT(ROUND(coverage, 2), '%') AS \"Coverage\", CONCAT(ROUND(duplicated_lines_density, 2), '%') AS \"Duplications\" FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/SteeringAdoptionTracker.json b/grafana/dashboards/postgresql/SteeringAdoptionTracker.json index a774b93949a..301cf5af7cf 100644 --- a/grafana/dashboards/postgresql/SteeringAdoptionTracker.json +++ b/grafana/dashboards/postgresql/SteeringAdoptionTracker.json @@ -62,7 +62,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -113,7 +113,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN is_spec_mode = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN is_spec_mode = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -164,7 +164,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS 'value' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -223,7 +223,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS 'Steering Rate', CAST(SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS 'Spec Mode Rate' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Steering Rate\", CAST(SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Spec Mode Rate\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -272,7 +272,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'With Steering' AS 'Mode', ROUND(AVG(prompt_length)) AS 'Avg Prompt', ROUND(AVG(response_length)) AS 'Avg Response' FROM _tool_q_dev_chat_log WHERE has_steering = TRUE AND $__timeFilter(timestamp) UNION ALL SELECT 'Without Steering', ROUND(AVG(prompt_length)), ROUND(AVG(response_length)) FROM _tool_q_dev_chat_log WHERE has_steering = FALSE AND $__timeFilter(timestamp)", + "rawSql": "SELECT 'With Steering' AS \"Mode\", ROUND(AVG(prompt_length)) AS \"Avg Prompt\", ROUND(AVG(response_length)) AS \"Avg Response\" FROM _tool_q_dev_chat_log WHERE has_steering = TRUE AND $__timeFilter(timestamp) UNION ALL SELECT 'Without Steering', ROUND(AVG(prompt_length)), ROUND(AVG(response_length)) FROM _tool_q_dev_chat_log WHERE has_steering = FALSE AND $__timeFilter(timestamp)", "refId": "A" } ], @@ -321,7 +321,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', COUNT(*) AS 'Total Chats', SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Spec Mode', ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS 'Steering %' FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", COUNT(*) AS \"Total Chats\", SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS \"Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS \"Spec Mode\", ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS \"Steering %\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/Taiga.json b/grafana/dashboards/postgresql/Taiga.json index 60205c69be0..393503b7079 100644 --- a/grafana/dashboards/postgresql/Taiga.json +++ b/grafana/dashboards/postgresql/Taiga.json @@ -1666,7 +1666,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS 'To Do', COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS 'In Progress', COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS 'Done', COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS 'Other' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS \"To Do\", COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS \"In Progress\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS \"Done\", COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS \"Other\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", "refId": "A" } ], @@ -2098,7 +2098,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS 'User Story', COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS 'Task', COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS 'Issue', COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS 'Epic' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Story\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Task\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issue\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", "refId": "A" } ], @@ -2272,7 +2272,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS 'User Stories', COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS 'Tasks', COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS 'Issues', COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS 'Epics' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Stories\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Tasks\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issues\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epics\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/WeeklyBugRetro.json b/grafana/dashboards/postgresql/WeeklyBugRetro.json index 31e4716d3c8..53585d93cfa 100644 --- a/grafana/dashboards/postgresql/WeeklyBugRetro.json +++ b/grafana/dashboards/postgresql/WeeklyBugRetro.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS 'Weekly New Bugs' FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly New Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -302,7 +302,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS 'Weekly Closed Bugs' FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly Closed Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -676,7 +676,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS 'Issue Number' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' GROUP BY 1", + "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS \"Issue Number\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' GROUP BY 1", "refId": "A", "select": [ [ @@ -835,7 +835,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', i.url AS 'Url', i.creator_name AS 'Creator' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", i.url AS \"Url\", i.creator_name AS \"Creator\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", "refId": "A", "select": [ [ @@ -1231,7 +1231,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS 'Lead Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS \"Lead Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", "refId": "A", "select": [ [ @@ -1768,7 +1768,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', priority AS 'Priority', severity AS 'Severity', CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) AND priority = ANY(ARRAY[${priority}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) AND priority = ANY(ARRAY[${priority}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1908,7 +1908,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CONCAT('#', i.issue_key) AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 2 DESC NULLS LAST LIMIT 100", + "rawSql": "SELECT CONCAT('#', i.issue_key) AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 2 DESC NULLS LAST LIMIT 100", "refId": "A", "select": [ [ @@ -2105,7 +2105,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS 'Issue Number', i.title AS 'Title', priority AS 'Priority', severity AS 'Severity', CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'Queue Time in Days', i.url AS 'Url' FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/WeeklyCommunityRetro.json b/grafana/dashboards/postgresql/WeeklyCommunityRetro.json index fbaebf9a5eb..aec3625dced 100644 --- a/grafana/dashboards/postgresql/WeeklyCommunityRetro.json +++ b/grafana/dashboards/postgresql/WeeklyCommunityRetro.json @@ -736,7 +736,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS 'queue_time_in_days', url FROM issue_comment_list WHERE comment_id IS NULL", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"queue_time_in_days\", url FROM issue_comment_list WHERE comment_id IS NULL", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/WorkLogs.json b/grafana/dashboards/postgresql/WorkLogs.json index 125e9797ccd..16137645920 100644 --- a/grafana/dashboards/postgresql/WorkLogs.json +++ b/grafana/dashboards/postgresql/WorkLogs.json @@ -216,7 +216,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(resolution_date, '%Y-%m-%d | %W') AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(authored_date, '%Y-%m-%d | %W') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(merged_date, '%Y-%m-%d | %W') AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN \"Date\" ELSE NULL END AS \"Date\", \"Time\", Activity, Details, Name FROM _activities", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(resolution_date, '%Y-%m-%d | %W') AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(authored_date, '%Y-%m-%d | %W') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(merged_date, '%Y-%m-%d | %W') AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN 'Date' ELSE NULL END AS \"Date\", \"Time\", Activity, Details, Name FROM _activities", "refId": "A", "sql": { "columns": [ @@ -580,7 +580,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activities_per_day AS (SELECT Date, SUM(CASE WHEN Activity = 'Create an issue' THEN 1 ELSE 0 END) AS 'Create an issue', SUM(CASE WHEN Activity = 'Issue resolved' THEN 1 ELSE 0 END) AS 'Issue resolved', SUM(CASE WHEN Activity = 'Finish a commit' THEN 1 ELSE 0 END) AS 'Finish a commit', SUM(CASE WHEN Activity = 'Open a PR' THEN 1 ELSE 0 END) AS 'Open a PR', SUM(CASE WHEN Activity = 'PR gets merged' THEN 1 ELSE 0 END) AS 'PR gets merged', SUM(CASE WHEN Activity = 'Comment on PR' THEN 1 ELSE 0 END) AS 'Comment on PR', SUM(CASE WHEN Activity = 'Review PR' THEN 1 ELSE 0 END) AS 'Review PR' FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS Date FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY Date DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(_last_7_days.Date, '%d/%m %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.Date = _activities_per_day.Date ORDER BY _last_7_days.Date ASC NULLS FIRST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activities_per_day AS (SELECT Date, SUM(CASE WHEN Activity = 'Create an issue' THEN 1 ELSE 0 END) AS \"Create an issue\", SUM(CASE WHEN Activity = 'Issue resolved' THEN 1 ELSE 0 END) AS \"Issue resolved\", SUM(CASE WHEN Activity = 'Finish a commit' THEN 1 ELSE 0 END) AS \"Finish a commit\", SUM(CASE WHEN Activity = 'Open a PR' THEN 1 ELSE 0 END) AS \"Open a PR\", SUM(CASE WHEN Activity = 'PR gets merged' THEN 1 ELSE 0 END) AS \"PR gets merged\", SUM(CASE WHEN Activity = 'Comment on PR' THEN 1 ELSE 0 END) AS \"Comment on PR\", SUM(CASE WHEN Activity = 'Review PR' THEN 1 ELSE 0 END) AS \"Review PR\" FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS Date FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY Date DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(_last_7_days.Date, '%d/%m %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.Date = _activities_per_day.Date ORDER BY _last_7_days.Date ASC NULLS FIRST", "refId": "A", "sql": { "columns": [ @@ -1226,7 +1226,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS 'PR Merged Size' FROM _pr_commits_data", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS \"PR Merged Size\" FROM _pr_commits_data", "refId": "A", "sql": { "columns": [ @@ -1371,7 +1371,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS 'Time to merge' FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS \"Time to merge\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/qdev_executive.json b/grafana/dashboards/postgresql/qdev_executive.json index 0aaf4dd3643..1311ecd3a09 100644 --- a/grafana/dashboards/postgresql/qdev_executive.json +++ b/grafana/dashboards/postgresql/qdev_executive.json @@ -120,7 +120,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'WAU' FROM lake._tool_q_dev_chat_log WHERE timestamp >= NOW() - INTERVAL '7 DAY'", + "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"WAU\" FROM lake._tool_q_dev_chat_log WHERE timestamp >= NOW() - INTERVAL '7 DAY'", "refId": "A" } ], @@ -179,7 +179,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(SUM(d.total_accepted), 0), 0), 2) AS 'Credits per Accepted Line' FROM (SELECT user_id, date, SUM(credits_used) AS credits_used FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r JOIN (SELECT user_id, date, (inline_ai_code_lines + chat_ai_code_lines + code_fix_accepted_lines + dev_accepted_lines) AS total_accepted FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)) AS d ON r.user_id = d.user_id AND r.date = d.date", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(SUM(d.total_accepted), 0), 0), 2) AS \"Credits per Accepted Line\" FROM (SELECT user_id, date, SUM(credits_used) AS credits_used FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r JOIN (SELECT user_id, date, (inline_ai_code_lines + chat_ai_code_lines + code_fix_accepted_lines + dev_accepted_lines) AS total_accepted FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)) AS d ON r.user_id = d.user_id AND r.date = d.date", "refId": "A" } ], @@ -238,7 +238,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1) AS 'Acceptance %' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1) AS \"Acceptance %\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -298,7 +298,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100, 0) AS 'Steering %' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100, 0) AS \"Steering %\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -403,7 +403,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, COUNT(DISTINCT user_id) AS 'Active Users' FROM (SELECT user_id, YEARWEEK(timestamp, 1) AS yw FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY yw ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, COUNT(DISTINCT user_id) AS \"Active Users\" FROM (SELECT user_id, YEARWEEK(timestamp, 1) AS yw FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY yw ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -495,7 +495,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, SUM(CASE WHEN yw = first_yw THEN 1 ELSE 0 END) AS 'New Users', SUM(CASE WHEN yw <> first_yw THEN 1 ELSE 0 END) AS 'Returning Users' FROM (SELECT DISTINCT u.user_id, YEARWEEK(u.timestamp, 1) AS yw, f.first_yw FROM lake._tool_q_dev_chat_log AS u JOIN (SELECT user_id, YEARWEEK(MIN(timestamp), 1) AS first_yw FROM lake._tool_q_dev_chat_log GROUP BY user_id) AS f ON u.user_id = f.user_id WHERE $__timeFilter(u.timestamp)) AS weekly GROUP BY yw ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, SUM(CASE WHEN yw = first_yw THEN 1 ELSE 0 END) AS \"New Users\", SUM(CASE WHEN yw <> first_yw THEN 1 ELSE 0 END) AS \"Returning Users\" FROM (SELECT DISTINCT u.user_id, YEARWEEK(u.timestamp, 1) AS yw, f.first_yw FROM lake._tool_q_dev_chat_log AS u JOIN (SELECT user_id, YEARWEEK(MIN(timestamp), 1) AS first_yw FROM lake._tool_q_dev_chat_log GROUP BY user_id) AS f ON u.user_id = f.user_id WHERE $__timeFilter(u.timestamp)) AS weekly GROUP BY yw ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -600,7 +600,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER BY date NULLS FIRST) AS 'Cumulative Credits', (SELECT CAST(SUM(credits_used) AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * DAY(CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE) AS DATE)) FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE))) AS 'Projected Monthly' FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE)) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER BY date NULLS FIRST) AS \"Cumulative Credits\", (SELECT CAST(SUM(credits_used) AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * DAY(CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE) AS DATE)) FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE))) AS \"Projected Monthly\" FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE)) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -662,7 +662,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', MAX(subscription_tier) AS 'Tier', ROUND(SUM(credits_used), 1) AS 'Total Credits Used', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY user_id HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", MAX(subscription_tier) AS \"Tier\", ROUND(SUM(credits_used), 1) AS \"Total Credits Used\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY user_id HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST", "refId": "A" } ], @@ -737,7 +737,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(d.display_name), d.user_id) AS 'User', COALESCE(MAX(r.subscription_tier), '') AS 'Tier', ROUND(SUM(r.credits_used), 1) AS 'Credits Used', SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) AS 'Total Accepted Lines', CASE WHEN SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) > 0 THEN ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines), 0), 2) ELSE NULL END AS 'Credits/Line', CONCAT(ROUND(CAST(SUM(d.inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(d.inline_suggestions_count), 0), 0) * 100, 1), '%') AS 'Accept Rate', SUM(d.code_review_findings_count) AS 'Review Findings', SUM(d.test_generation_event_count) AS 'Test Gen Events', SUM(d.dev_accepted_lines) AS 'Agentic Lines', MIN(d.date) AS 'First Active', MAX(d.date) AS 'Last Active' FROM lake._tool_q_dev_user_data AS d LEFT JOIN (SELECT user_id, date, SUM(credits_used) AS credits_used, MAX(subscription_tier) AS subscription_tier FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r ON d.user_id = r.user_id AND d.date = r.date WHERE $__timeFilter(d.date) GROUP BY d.user_id ORDER BY SUM(r.credits_used) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(d.display_name), d.user_id) AS \"User\", COALESCE(MAX(r.subscription_tier), '') AS \"Tier\", ROUND(SUM(r.credits_used), 1) AS \"Credits Used\", SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) AS \"Total Accepted Lines\", CASE WHEN SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) > 0 THEN ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines), 0), 2) ELSE NULL END AS \"Credits/Line\", CONCAT(ROUND(CAST(SUM(d.inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(d.inline_suggestions_count), 0), 0) * 100, 1), '%') AS \"Accept Rate\", SUM(d.code_review_findings_count) AS \"Review Findings\", SUM(d.test_generation_event_count) AS \"Test Gen Events\", SUM(d.dev_accepted_lines) AS \"Agentic Lines\", MIN(d.date) AS \"First Active\", MAX(d.date) AS \"Last Active\" FROM lake._tool_q_dev_user_data AS d LEFT JOIN (SELECT user_id, date, SUM(credits_used) AS credits_used, MAX(subscription_tier) AS subscription_tier FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r ON d.user_id = r.user_id AND d.date = r.date WHERE $__timeFilter(d.date) GROUP BY d.user_id ORDER BY SUM(r.credits_used) DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/qdev_feature_metrics.json b/grafana/dashboards/postgresql/qdev_feature_metrics.json index f33bf1b3f06..181532e6781 100644 --- a/grafana/dashboards/postgresql/qdev_feature_metrics.json +++ b/grafana/dashboards/postgresql/qdev_feature_metrics.json @@ -70,7 +70,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Users', SUM(inline_suggestions_count) AS 'Inline Suggestions', SUM(inline_acceptance_count) AS 'Inline Accepted', SUM(chat_messages_sent) AS 'Chat Messages', SUM(chat_ai_code_lines) AS 'Chat AI Lines', SUM(code_review_findings_count) AS 'Review Findings', SUM(test_generation_event_count) AS 'Test Gen Events', SUM(dev_accepted_lines) AS 'Agentic Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"Active Users\", SUM(inline_suggestions_count) AS \"Inline Suggestions\", SUM(inline_acceptance_count) AS \"Inline Accepted\", SUM(chat_messages_sent) AS \"Chat Messages\", SUM(chat_ai_code_lines) AS \"Chat AI Lines\", SUM(code_review_findings_count) AS \"Review Findings\", SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(dev_accepted_lines) AS \"Agentic Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -174,7 +174,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(inline_suggestions_count) AS 'Suggestions', SUM(inline_acceptance_count) AS 'Accepted', SUM(inline_ai_code_lines) AS 'AI Code Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", SUM(inline_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -265,7 +265,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Inline Suggestions', CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Code Fix', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Inline Chat' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -369,7 +369,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS 'Messages Sent', SUM(chat_messages_interacted) AS 'Messages Interacted', SUM(chat_ai_code_lines) AS 'AI Code Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Messages Sent\", SUM(chat_messages_interacted) AS \"Messages Interacted\", SUM(chat_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -460,7 +460,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS 'Generation Events', SUM(dev_generated_lines) AS 'Generated Lines', SUM(dev_accepted_lines) AS 'Accepted Lines', SUM(dev_acceptance_event_count) AS 'Acceptance Events' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Generation Events\", SUM(dev_generated_lines) AS \"Generated Lines\", SUM(dev_accepted_lines) AS \"Accepted Lines\", SUM(dev_acceptance_event_count) AS \"Acceptance Events\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -564,7 +564,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(code_review_findings_count) AS 'Review Findings', SUM(code_review_succeeded_event_count) AS 'Reviews Succeeded', SUM(code_review_failed_event_count) AS 'Reviews Failed' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(code_review_findings_count) AS \"Review Findings\", SUM(code_review_succeeded_event_count) AS \"Reviews Succeeded\", SUM(code_review_failed_event_count) AS \"Reviews Failed\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -655,7 +655,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS 'Test Gen Events', SUM(test_generation_generated_tests) AS 'Tests Generated', SUM(test_generation_accepted_tests) AS 'Tests Accepted', SUM(test_generation_generated_lines) AS 'Lines Generated', SUM(test_generation_accepted_lines) AS 'Lines Accepted' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(test_generation_generated_tests) AS \"Tests Generated\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(test_generation_generated_lines) AS \"Lines Generated\", SUM(test_generation_accepted_lines) AS \"Lines Accepted\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -746,7 +746,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS 'Doc Gen Events', SUM(doc_generation_accepted_line_additions) AS 'Doc Lines Accepted', SUM(transformation_event_count) AS 'Transformation Events', SUM(transformation_lines_generated) AS 'Transform Lines Generated' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(doc_generation_accepted_line_additions) AS \"Doc Lines Accepted\", SUM(transformation_event_count) AS \"Transformation Events\", SUM(transformation_lines_generated) AS \"Transform Lines Generated\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -917,7 +917,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', SUM(inline_suggestions_count) AS 'Suggestions', SUM(inline_acceptance_count) AS 'Accepted', CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1), '%') AS 'Accept %', SUM(chat_messages_sent) AS 'Chat Msgs', SUM(chat_ai_code_lines) AS 'Chat Lines', SUM(dev_accepted_lines) AS 'Agentic Lines', SUM(code_review_findings_count) AS 'Review Findings', SUM(test_generation_accepted_tests) AS 'Tests Accepted', SUM(doc_generation_event_count) AS 'Doc Gen', SUM(transformation_event_count) AS 'Transforms', MIN(date) AS 'First Active', MAX(date) AS 'Last Active' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_suggestions_count) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1), '%') AS \"Accept %\", SUM(chat_messages_sent) AS \"Chat Msgs\", SUM(chat_ai_code_lines) AS \"Chat Lines\", SUM(dev_accepted_lines) AS \"Agentic Lines\", SUM(code_review_findings_count) AS \"Review Findings\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(doc_generation_event_count) AS \"Doc Gen\", SUM(transformation_event_count) AS \"Transforms\", MIN(date) AS \"First Active\", MAX(date) AS \"Last Active\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_suggestions_count) DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/qdev_logging.json b/grafana/dashboards/postgresql/qdev_logging.json index 5630c69cb8c..8d06a52b7f2 100644 --- a/grafana/dashboards/postgresql/qdev_logging.json +++ b/grafana/dashboards/postgresql/qdev_logging.json @@ -70,7 +70,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT (SELECT COUNT(*) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Chat Events', (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Chat Users', (SELECT COUNT(DISTINCT conversation_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) AND conversation_id <> '') AS 'Conversations', (SELECT COUNT(*) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS 'Completion Events', (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS 'Completion Users', (SELECT SUM(code_reference_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Code References', (SELECT SUM(web_link_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS 'Web Links Cited'", + "rawSql": "SELECT (SELECT COUNT(*) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS \"Chat Events\", (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS \"Chat Users\", (SELECT COUNT(DISTINCT conversation_id) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) AND conversation_id <> '') AS \"Conversations\", (SELECT COUNT(*) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS \"Completion Events\", (SELECT COUNT(DISTINCT user_id) FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp)) AS \"Completion Users\", (SELECT SUM(code_reference_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS \"Code References\", (SELECT SUM(web_link_count) FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS \"Web Links Cited\"", "refId": "A" } ], @@ -159,7 +159,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT LPAD(CAST(hour_of_day AS TEXT), 2, '0') AS 'Hour', SUM(chat_count) AS 'Chat Events', SUM(completion_count) AS 'Completion Events' FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, COUNT(*) AS chat_count, 0 AS completion_count FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 0 AS chat_count, COUNT(*) AS completion_count FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS combined GROUP BY hour_of_day ORDER BY hour_of_day NULLS FIRST", + "rawSql": "SELECT LPAD(CAST(hour_of_day AS TEXT), 2, '0') AS \"Hour\", SUM(chat_count) AS \"Chat Events\", SUM(completion_count) AS \"Completion Events\" FROM (SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, COUNT(*) AS chat_count, 0 AS completion_count FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp) UNION ALL SELECT EXTRACT(HOUR FROM timestamp) AS hour_of_day, 0 AS chat_count, COUNT(*) AS completion_count FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY EXTRACT(HOUR FROM timestamp)) AS combined GROUP BY hour_of_day ORDER BY hour_of_day NULLS FIRST", "refId": "A" } ], @@ -227,7 +227,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS 'Trigger Type', COUNT(*) AS 'Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY chat_trigger_type ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS \"Trigger Type\", COUNT(*) AS \"Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY chat_trigger_type ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -295,7 +295,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS 'Model', COUNT(*) AS 'Requests' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS \"Model\", COUNT(*) AS \"Requests\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -363,7 +363,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS 'File Type', COUNT(*) AS 'Completions' FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"File Type\", COUNT(*) AS \"Completions\" FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", "refId": "A" } ], @@ -455,7 +455,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CAST(COUNT(*) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END), 0), 0) AS 'Avg Turns per Conversation', COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS 'Unique Conversations', COUNT(*) AS 'Total Chat Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CAST(COUNT(*) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END), 0), 0) AS \"Avg Turns per Conversation\", COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS \"Unique Conversations\", COUNT(*) AS \"Total Chat Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", "refId": "A" } ], @@ -547,7 +547,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(chat) AS 'Chat Events', SUM(completions) AS 'Completion Events' FROM (SELECT CAST(timestamp AS DATE) AS time, COUNT(*) AS chat, 0 AS completions FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) UNION ALL SELECT CAST(timestamp AS DATE) AS time, 0 AS chat, COUNT(*) AS completions FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE)) AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(chat) AS \"Chat Events\", SUM(completions) AS \"Completion Events\" FROM (SELECT CAST(timestamp AS DATE) AS time, COUNT(*) AS chat, 0 AS completions FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) UNION ALL SELECT CAST(timestamp AS DATE) AS time, 0 AS chat, COUNT(*) AS completions FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE)) AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -609,7 +609,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(u.display_name, u.user_id) AS 'User', u.user_id AS 'User ID', u.chat_events AS 'Chat Events', u.conversations AS 'Conversations', ROUND(CAST(u.chat_events AS NUMERIC) / NULLIF(NULLIF(u.conversations, 0), 0), 1) AS 'Avg Turns', COALESCE(c.completion_events, 0) AS 'Completion Events', COALESCE(c.files_count, 0) AS 'Distinct Files', ROUND(u.avg_prompt_len) AS 'Avg Prompt Len', ROUND(u.avg_response_len) AS 'Avg Response Len', u.steering_count AS 'Steering Uses', u.spec_count AS 'Spec Mode Uses', u.code_ref_count AS 'Code Refs', u.web_link_count AS 'Web Links', u.models_used AS 'Models Used', u.first_seen AS 'First Seen', GREATEST(u.last_seen, COALESCE(c.last_seen, u.last_seen)) AS 'Last Seen' FROM (SELECT user_id, MAX(display_name) AS display_name, COUNT(*) AS chat_events, COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS conversations, AVG(prompt_length) AS avg_prompt_len, AVG(response_length) AS avg_response_len, STRING_AGG(DISTINCT CASE WHEN model_id <> '' AND NOT model_id IS NULL THEN model_id END, ', ' ORDER BY model_id NULLS FIRST) AS models_used, SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS steering_count, SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS spec_count, SUM(code_reference_count) AS code_ref_count, SUM(web_link_count) AS web_link_count, MIN(timestamp) AS first_seen, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS u LEFT JOIN (SELECT user_id, COUNT(*) AS completion_events, COUNT(DISTINCT file_name) AS files_count, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS c ON u.user_id = c.user_id ORDER BY u.user_id NULLS FIRST", + "rawSql": "SELECT COALESCE(u.display_name, u.user_id) AS \"User\", u.user_id AS \"User ID\", u.chat_events AS \"Chat Events\", u.conversations AS \"Conversations\", ROUND(CAST(u.chat_events AS NUMERIC) / NULLIF(NULLIF(u.conversations, 0), 0), 1) AS \"Avg Turns\", COALESCE(c.completion_events, 0) AS \"Completion Events\", COALESCE(c.files_count, 0) AS \"Distinct Files\", ROUND(u.avg_prompt_len) AS \"Avg Prompt Len\", ROUND(u.avg_response_len) AS \"Avg Response Len\", u.steering_count AS \"Steering Uses\", u.spec_count AS \"Spec Mode Uses\", u.code_ref_count AS \"Code Refs\", u.web_link_count AS \"Web Links\", u.models_used AS \"Models Used\", u.first_seen AS \"First Seen\", GREATEST(u.last_seen, COALESCE(c.last_seen, u.last_seen)) AS \"Last Seen\" FROM (SELECT user_id, MAX(display_name) AS display_name, COUNT(*) AS chat_events, COUNT(DISTINCT CASE WHEN conversation_id <> '' THEN conversation_id END) AS conversations, AVG(prompt_length) AS avg_prompt_len, AVG(response_length) AS avg_response_len, STRING_AGG(DISTINCT CASE WHEN model_id <> '' AND NOT model_id IS NULL THEN model_id END, ', ' ORDER BY model_id NULLS FIRST) AS models_used, SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS steering_count, SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS spec_count, SUM(code_reference_count) AS code_ref_count, SUM(web_link_count) AS web_link_count, MIN(timestamp) AS first_seen, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS u LEFT JOIN (SELECT user_id, COUNT(*) AS completion_events, COUNT(DISTINCT file_name) AS files_count, MAX(timestamp) AS last_seen FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY user_id) AS c ON u.user_id = c.user_id ORDER BY u.user_id NULLS FIRST", "refId": "A" } ], @@ -677,7 +677,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS 'Using Steering', SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS 'Using Spec Mode', SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS 'Plain Chat' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS \"Using Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS \"Using Spec Mode\", SUM(CASE WHEN has_steering = FALSE AND is_spec_mode = FALSE THEN 1 ELSE 0 END) AS \"Plain Chat\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -745,7 +745,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file active)' ELSE active_file_extension END AS 'File Type', COUNT(*) AS 'Chat Events' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file active)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", "refId": "A" } ], @@ -813,7 +813,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(CASE WHEN code_reference_count > 0 THEN 1 ELSE 0 END) AS 'With Code References', SUM(CASE WHEN web_link_count > 0 THEN 1 ELSE 0 END) AS 'With Web Links', SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS 'With Followup Prompts', SUM(CASE WHEN code_reference_count = 0 AND web_link_count = 0 AND has_followup_prompts = FALSE THEN 1 ELSE 0 END) AS 'Plain Response' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT SUM(CASE WHEN code_reference_count > 0 THEN 1 ELSE 0 END) AS \"With Code References\", SUM(CASE WHEN web_link_count > 0 THEN 1 ELSE 0 END) AS \"With Web Links\", SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS \"With Followup Prompts\", SUM(CASE WHEN code_reference_count = 0 AND web_link_count = 0 AND has_followup_prompts = FALSE THEN 1 ELSE 0 END) AS \"Plain Response\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -905,7 +905,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, AVG(prompt_length) AS 'Avg Prompt Length', AVG(response_length) AS 'Avg Response Length', MAX(prompt_length) AS 'Max Prompt Length', MAX(response_length) AS 'Max Response Length' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, AVG(prompt_length) AS \"Avg Prompt Length\", AVG(response_length) AS \"Avg Response Length\", MAX(prompt_length) AS \"Max Prompt Length\", MAX(response_length) AS \"Max Response Length\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", "refId": "A" } ], @@ -996,7 +996,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, ROUND(AVG(left_context_length)) AS 'Avg Left Context', ROUND(AVG(right_context_length)) AS 'Avg Right Context', ROUND(AVG(left_context_length + right_context_length)) AS 'Avg Total Context' FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, ROUND(AVG(left_context_length)) AS \"Avg Left Context\", ROUND(AVG(right_context_length)) AS \"Avg Right Context\", ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\" FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", "refId": "A" } ], @@ -1087,7 +1087,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, SUM(code_reference_count) AS 'Code References', SUM(web_link_count) AS 'Web Links', SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS 'Followup Prompts' FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, SUM(code_reference_count) AS \"Code References\", SUM(web_link_count) AS \"Web Links\", SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS \"Followup Prompts\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) ORDER BY CAST(timestamp AS DATE) NULLS FIRST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/qdev_user_data.json b/grafana/dashboards/postgresql/qdev_user_data.json index 5afd9d8f184..a8f83d8a833 100644 --- a/grafana/dashboards/postgresql/qdev_user_data.json +++ b/grafana/dashboards/postgresql/qdev_user_data.json @@ -72,7 +72,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_id) AS 'Active Users', SUM(chat_ai_code_lines) AS 'Accepted Lines (Chat)', SUM(inline_ai_code_lines) AS 'Accepted Lines (Inline Suggestion)', CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Acceptance Rate (Inline Suggestion)', SUM(code_review_findings_count) AS 'Findings (Code Review)', SUM(code_fix_accepted_lines) AS 'Accepted Lines (Code Fix)', CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Acceptance Rate (Code Fix)', SUM(transformation_lines_ingested) AS 'Ingested Lines (Java Transform)', SUM(transformation_lines_generated) AS 'Generated Lines (Java Transform)', SUM(inline_chat_accepted_line_additions) AS 'Accepted Lines (Inline Chat)', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Acceptance Rate (Inline Chat)' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT COUNT(DISTINCT user_id) AS \"Active Users\", SUM(chat_ai_code_lines) AS \"Accepted Lines (Chat)\", SUM(inline_ai_code_lines) AS \"Accepted Lines (Inline Suggestion)\", CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Acceptance Rate (Inline Suggestion)\", SUM(code_review_findings_count) AS \"Findings (Code Review)\", SUM(code_fix_accepted_lines) AS \"Accepted Lines (Code Fix)\", CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Acceptance Rate (Code Fix)\", SUM(transformation_lines_ingested) AS \"Ingested Lines (Java Transform)\", SUM(transformation_lines_generated) AS \"Generated Lines (Java Transform)\", SUM(inline_chat_accepted_line_additions) AS \"Accepted Lines (Inline Chat)\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Acceptance Rate (Inline Chat)\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A", "select": [ [ @@ -201,7 +201,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_ai_code_lines) AS 'Chat Accepted Lines', SUM(code_fix_accepted_lines) AS 'Code Fix Accepted Lines', SUM(code_fix_generated_lines) AS 'Code Fix Generated Lines', SUM(transformation_lines_ingested) AS 'Java Transform Ingested Lines', SUM(transformation_lines_generated) AS 'Java Transform Generated Lines', SUM(inline_ai_code_lines) AS 'Inline Suggestion Accepted Lines', SUM(inline_chat_accepted_line_additions) AS 'Inline Chat Accepted Line Additions', SUM(inline_chat_accepted_line_deletions) AS 'Inline Chat Accepted Line Deletions', SUM(inline_chat_dismissed_line_additions) AS 'Inline Chat Dismissed Line Additions', SUM(inline_chat_dismissed_line_deletions) AS 'Inline Chat Dismissed Line Deletions', SUM(inline_chat_rejected_line_additions) AS 'Inline Chat Rejected Line Additions', SUM(inline_chat_rejected_line_deletions) AS 'Inline Chat Rejected Line Deletions' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_ai_code_lines) AS \"Chat Accepted Lines\", SUM(code_fix_accepted_lines) AS \"Code Fix Accepted Lines\", SUM(code_fix_generated_lines) AS \"Code Fix Generated Lines\", SUM(transformation_lines_ingested) AS \"Java Transform Ingested Lines\", SUM(transformation_lines_generated) AS \"Java Transform Generated Lines\", SUM(inline_ai_code_lines) AS \"Inline Suggestion Accepted Lines\", SUM(inline_chat_accepted_line_additions) AS \"Inline Chat Accepted Line Additions\", SUM(inline_chat_accepted_line_deletions) AS \"Inline Chat Accepted Line Deletions\", SUM(inline_chat_dismissed_line_additions) AS \"Inline Chat Dismissed Line Additions\", SUM(inline_chat_dismissed_line_deletions) AS \"Inline Chat Dismissed Line Deletions\", SUM(inline_chat_rejected_line_additions) AS \"Inline Chat Rejected Line Additions\", SUM(inline_chat_rejected_line_deletions) AS \"Inline Chat Rejected Line Deletions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -330,7 +330,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS 'Chat Messages Sent', SUM(code_fix_acceptance_event_count) AS 'Code Fix Accepted Event Count', SUM(code_fix_generation_event_count) AS 'Code Fix Generated Event Count', SUM(transformation_event_count) AS 'Java Transform Event Count', SUM(inline_acceptance_count) AS 'Inline Suggestion Accepted Suggestions', SUM(inline_suggestions_count) AS 'Inline Suggestion Count', SUM(inline_chat_total_event_count) AS 'Inline Chat Total Suggestions', SUM(inline_chat_acceptance_event_count) AS 'Inline Chat Accepted Suggestions', SUM(inline_chat_dismissal_event_count) AS 'Inline Chat Dismissed Suggestions', SUM(inline_chat_rejection_event_count) AS 'Inline Chat Rejected Suggestions' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Chat Messages Sent\", SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(transformation_event_count) AS \"Java Transform Event Count\", SUM(inline_acceptance_count) AS \"Inline Suggestion Accepted Suggestions\", SUM(inline_suggestions_count) AS \"Inline Suggestion Count\", SUM(inline_chat_total_event_count) AS \"Inline Chat Total Suggestions\", SUM(inline_chat_acceptance_event_count) AS \"Inline Chat Accepted Suggestions\", SUM(inline_chat_dismissal_event_count) AS \"Inline Chat Dismissed Suggestions\", SUM(inline_chat_rejection_event_count) AS \"Inline Chat Rejected Suggestions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -458,7 +458,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(code_fix_acceptance_event_count) AS 'Code Fix Accepted Event Count', SUM(code_fix_generation_event_count) AS 'Code Fix Generated Event Count', SUM(code_review_findings_count) AS 'Total Findings' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(code_review_findings_count) AS \"Total Findings\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -587,7 +587,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS 'Code Fix', CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS 'Inline Suggestions', CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS 'Inline Chat' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -730,7 +730,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', SUM(chat_ai_code_lines) AS 'Accepted Lines (Chat)', SUM(transformation_lines_ingested) AS 'Lines Ingested (Java Transform)', SUM(transformation_lines_generated) AS 'Lines Generated (Java Transform)', SUM(transformation_event_count) AS 'Event Count (Java Transform)', SUM(code_review_findings_count) AS 'Findings (Code Review)', SUM(code_fix_accepted_lines) AS 'Accepted Lines (Code Fix)', SUM(code_fix_generated_lines) AS 'Generated Lines (Code Fix)', SUM(code_fix_acceptance_event_count) AS 'Accepted Count (Code Fix)', SUM(code_fix_generation_event_count) AS 'Generated Count (Code Fix)', CONCAT(ROUND(CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Code Fix)', SUM(inline_ai_code_lines) AS 'Accepted Lines (Inline Suggestion)', SUM(inline_acceptance_count) AS 'Accepted Count (Inline Suggestion)', SUM(inline_suggestions_count) AS 'Total Count (Inline Suggestion)', CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Inline Suggestion)', SUM(inline_chat_accepted_line_additions) AS 'Accepted Line Additions (Inline Chat)', SUM(inline_chat_accepted_line_deletions) AS 'Accepted Line Deletions (Inline Chat)', SUM(inline_chat_acceptance_event_count) AS 'Accepted Events (Inline Chat)', SUM(inline_chat_total_event_count) AS 'Total Events (Inline Chat)', CONCAT(ROUND(CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) * 100, 2), '%') AS 'Acceptance Rate (Inline Chat)', SUM(doc_generation_event_count) AS 'Doc Gen Events', SUM(test_generation_event_count) AS 'Test Gen Events', SUM(dev_accepted_lines) AS 'Dev Accepted Lines', MIN(date) AS 'First Activity', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_ai_code_lines) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(chat_ai_code_lines) AS \"Accepted Lines (Chat)\", SUM(transformation_lines_ingested) AS \"Lines Ingested (Java Transform)\", SUM(transformation_lines_generated) AS \"Lines Generated (Java Transform)\", SUM(transformation_event_count) AS \"Event Count (Java Transform)\", SUM(code_review_findings_count) AS \"Findings (Code Review)\", SUM(code_fix_accepted_lines) AS \"Accepted Lines (Code Fix)\", SUM(code_fix_generated_lines) AS \"Generated Lines (Code Fix)\", SUM(code_fix_acceptance_event_count) AS \"Accepted Count (Code Fix)\", SUM(code_fix_generation_event_count) AS \"Generated Count (Code Fix)\", CONCAT(ROUND(CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Code Fix)\", SUM(inline_ai_code_lines) AS \"Accepted Lines (Inline Suggestion)\", SUM(inline_acceptance_count) AS \"Accepted Count (Inline Suggestion)\", SUM(inline_suggestions_count) AS \"Total Count (Inline Suggestion)\", CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Inline Suggestion)\", SUM(inline_chat_accepted_line_additions) AS \"Accepted Line Additions (Inline Chat)\", SUM(inline_chat_accepted_line_deletions) AS \"Accepted Line Deletions (Inline Chat)\", SUM(inline_chat_acceptance_event_count) AS \"Accepted Events (Inline Chat)\", SUM(inline_chat_total_event_count) AS \"Total Events (Inline Chat)\", CONCAT(ROUND(CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Inline Chat)\", SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_ai_code_lines) DESC NULLS LAST", "refId": "A", "select": [ [ @@ -859,7 +859,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS 'Doc Generation Events', SUM(doc_generation_accepted_line_additions) AS 'Accepted Line Additions', SUM(doc_generation_accepted_line_updates) AS 'Accepted Line Updates', SUM(doc_generation_rejected_line_additions) AS 'Rejected Line Additions', SUM(doc_generation_rejected_line_updates) AS 'Rejected Line Updates' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Generation Events\", SUM(doc_generation_accepted_line_additions) AS \"Accepted Line Additions\", SUM(doc_generation_accepted_line_updates) AS \"Accepted Line Updates\", SUM(doc_generation_rejected_line_additions) AS \"Rejected Line Additions\", SUM(doc_generation_rejected_line_updates) AS \"Rejected Line Updates\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -988,7 +988,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS 'Test Generation Events', SUM(test_generation_accepted_tests) AS 'Accepted Tests', SUM(test_generation_generated_tests) AS 'Generated Tests', SUM(test_generation_accepted_lines) AS 'Accepted Lines', SUM(test_generation_generated_lines) AS 'Generated Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Generation Events\", SUM(test_generation_accepted_tests) AS \"Accepted Tests\", SUM(test_generation_generated_tests) AS \"Generated Tests\", SUM(test_generation_accepted_lines) AS \"Accepted Lines\", SUM(test_generation_generated_lines) AS \"Generated Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -1117,7 +1117,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS 'Dev Generation Events', SUM(dev_acceptance_event_count) AS 'Dev Acceptance Events', SUM(dev_generated_lines) AS 'Dev Generated Lines', SUM(dev_accepted_lines) AS 'Dev Accepted Lines' FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Dev Generation Events\", SUM(dev_acceptance_event_count) AS \"Dev Acceptance Events\", SUM(dev_generated_lines) AS \"Dev Generated Lines\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/qdev_user_report.json b/grafana/dashboards/postgresql/qdev_user_report.json index 692ab01f96c..65111016a1e 100644 --- a/grafana/dashboards/postgresql/qdev_user_report.json +++ b/grafana/dashboards/postgresql/qdev_user_report.json @@ -70,7 +70,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(credits_used) AS 'Total Credits Used', COUNT(DISTINCT user_id) AS 'Active Users', SUM(total_messages) AS 'Total Messages', SUM(chat_conversations) AS 'Total Conversations' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date)", + "rawSql": "SELECT SUM(credits_used) AS \"Total Credits Used\", COUNT(DISTINCT user_id) AS \"Active Users\", SUM(total_messages) AS \"Total Messages\", SUM(chat_conversations) AS \"Total Conversations\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date)", "refId": "A" } ], @@ -254,7 +254,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(CASE WHEN client_type = 'KIRO_IDE' THEN total_messages ELSE 0 END) AS 'Messages (IDE)', SUM(CASE WHEN client_type = 'KIRO_CLI' THEN total_messages ELSE 0 END) AS 'Messages (CLI)', SUM(CASE WHEN client_type = 'PLUGIN' THEN total_messages ELSE 0 END) AS 'Messages (Plugin)', SUM(chat_conversations) AS 'Conversations' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(CASE WHEN client_type = 'KIRO_IDE' THEN total_messages ELSE 0 END) AS \"Messages (IDE)\", SUM(CASE WHEN client_type = 'KIRO_CLI' THEN total_messages ELSE 0 END) AS \"Messages (CLI)\", SUM(CASE WHEN client_type = 'PLUGIN' THEN total_messages ELSE 0 END) AS \"Messages (Plugin)\", SUM(chat_conversations) AS \"Conversations\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -322,7 +322,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT subscription_tier AS 'Tier', COUNT(DISTINCT user_id) AS 'Users' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY subscription_tier ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST", + "rawSql": "SELECT subscription_tier AS \"Tier\", COUNT(DISTINCT user_id) AS \"Users\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY subscription_tier ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST", "refId": "A" } ], @@ -433,7 +433,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS 'User', subscription_tier AS 'Tier', client_type AS 'Client', SUM(credits_used) AS 'Credits Used', SUM(total_messages) AS 'Messages', SUM(chat_conversations) AS 'Conversations', SUM(overage_credits_used) AS 'Overage Credits', CASE WHEN MAX(CAST(overage_enabled AS UBIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS 'Overage', MIN(date) AS 'First Activity', MAX(date) AS 'Last Activity' FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, subscription_tier, client_type ORDER BY user_id DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", subscription_tier AS \"Tier\", client_type AS \"Client\", SUM(credits_used) AS \"Credits Used\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\", SUM(overage_credits_used) AS \"Overage Credits\", CASE WHEN MAX(CAST(overage_enabled AS UBIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS \"Overage\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, subscription_tier, client_type ORDER BY user_id DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/provisioning/datasources/datasource.yml b/grafana/provisioning/datasources/datasource.yml deleted file mode 100644 index c2afa5aa0ca..00000000000 --- a/grafana/provisioning/datasources/datasource.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# config file version -apiVersion: 1 - -datasources: - - name: mysql - type: mysql - url: $MYSQL_URL - database: $MYSQL_DATABASE - user: $MYSQL_USER - secureJsonData: - password: $MYSQL_PASSWORD - editable: false diff --git a/grafana/scripts/convert-mysql-to-postgresql.py b/grafana/scripts/convert-mysql-to-postgresql.py index 4185d12e267..2c6db19988e 100755 --- a/grafana/scripts/convert-mysql-to-postgresql.py +++ b/grafana/scripts/convert-mysql-to-postgresql.py @@ -48,6 +48,61 @@ def convert_sql_mysql_to_postgres(sql: str) -> str: # Restore string literals converted = restore_string_literals(converted, string_literals) + # Convert MySQL date format patterns to PostgreSQL in TO_CHAR (after string restoration) + # MySQL uses %Y-%m, PostgreSQL uses YYYY-MM + converted = re.sub( + r"TO_CHAR\s*\(\s*([^,]+?)\s*,\s*'%Y-%m'\s*\)", + r"TO_CHAR(\1, 'YYYY-MM')", + converted, + flags=re.IGNORECASE + ) + converted = re.sub( + r"TO_CHAR\s*\(\s*([^,]+?)\s*,\s*'%y/%m'\s*\)", + r"TO_CHAR(\1, 'YY/MM')", + converted, + flags=re.IGNORECASE + ) + + # DIV(TO_CHAR(date, 'YYYY-MM'), N) -> calculate month-based division (after format conversion) + # Used for bucketing dates into N-month periods (e.g., half-years with N=6) + converted = re.sub( + r'DIV\s*\(\s*TO_CHAR\s*\(\s*([^,]+?)\s*,\s*["\']YYYY-MM["\']\s*\)\s*,\s*(\d+)\s*\)', + r'((EXTRACT(YEAR FROM \1) * 12 + EXTRACT(MONTH FROM \1)) / \2)', + converted, + flags=re.IGNORECASE + ) + + # Convert AS 'alias' to AS "alias" after string restoration + # PostgreSQL doesn't accept single quotes for column aliases + converted = re.sub(r'\bAS\s+\'([^\']+)\'', r'AS "\1"', converted, flags=re.IGNORECASE) + + # Convert LIKE/NOT LIKE with double-quoted patterns to single quotes + # MySQL accepts both, PostgreSQL only accepts single quotes for string literals + # Double quotes in PostgreSQL are for identifiers, not strings + converted = re.sub(r'\bLIKE\s+"([^"]+)"', r"LIKE '\1'", converted, flags=re.IGNORECASE) + converted = re.sub(r'\bNOT\s+LIKE\s+"([^"]+)"', r"NOT LIKE '\1'", converted, flags=re.IGNORECASE) + + # Convert double-quoted string literals to single quotes + # Pattern: = "value", <> "value", != "value", IN ("val1", "val2"), CONCAT(..., "text", ...), THEN "value" + # But skip: column aliases after AS (already handled above as AS "alias") + # MySQL accepts both quotes for strings, PostgreSQL only single quotes + converted = re.sub(r'(=|<>|!=)\s+"([^"]+)"', r"\1 '\2'", converted) + converted = re.sub(r'\bIN\s*\(\s*"([^"]+)"\s*\)', r"IN ('\1')", converted, flags=re.IGNORECASE) + + # Convert double-quoted literals inside CONCAT to single quotes + # CONCAT(..., "(text)", ...) → CONCAT(..., '(text)', ...) + def replace_concat_strings(match): + content = match.group(0) + # Replace double-quoted strings inside CONCAT with single quotes + content = re.sub(r'"([^"]+)"', r"'\1'", content) + return content + + converted = re.sub(r'\bCONCAT\s*\([^)]+\)', replace_concat_strings, converted, flags=re.IGNORECASE) + + # Convert THEN/ELSE double-quoted strings to single quotes + # THEN "text" → THEN 'text', ELSE "text" → ELSE 'text' + converted = re.sub(r'\b(THEN|ELSE)\s+"([^"]+)"', r"\1 '\2'", converted, flags=re.IGNORECASE) + return converted except Exception as e: # Fallback to regex-based conversion for complex cases @@ -59,6 +114,7 @@ def protect_string_literals(sql_text: str) -> tuple[str, dict]: """ Replace all string literals with placeholders to protect them from conversion. Returns modified SQL and mapping of placeholders to original strings. + Handles SQL comments (-- and /* */) to avoid treating quotes in comments as string delimiters. """ literals = {} counter = 0 @@ -66,6 +122,30 @@ def protect_string_literals(sql_text: str) -> tuple[str, dict]: i = 0 while i < len(sql_text): + # Skip single-line comments (-- comment) + if i < len(sql_text) - 1 and sql_text[i:i+2] == '--': + # Find end of line + end = sql_text.find('\n', i) + if end == -1: + result.append(sql_text[i:]) + break + else: + result.append(sql_text[i:end+1]) + i = end + 1 + continue + + # Skip multi-line comments (/* comment */) + if i < len(sql_text) - 1 and sql_text[i:i+2] == '/*': + end = sql_text.find('*/', i + 2) + if end == -1: + result.append(sql_text[i:]) + break + else: + result.append(sql_text[i:end+2]) + i = end + 2 + continue + + # Process string literals if sql_text[i] in ("'", '"'): quote_char = sql_text[i] literal_parts = [sql_text[i]] @@ -402,15 +482,6 @@ def replace_timestampdiff(args): replace_timestampdiff ) - # DIV(TO_CHAR(date, 'YYYY-MM'), N) -> calculate month-based division - # Used for bucketing dates into N-month periods (e.g., half-years with N=6) - sql = re.sub( - r'DIV\s*\(\s*TO_CHAR\s*\(\s*([^,]+?)\s*,\s*["\']YYYY-MM["\']\s*\)\s*,\s*(\d+)\s*\)', - r'((EXTRACT(YEAR FROM \1) * 12 + EXTRACT(MONTH FROM \1)) / \2)', - sql, - flags=re.IGNORECASE - ) - return sql @@ -525,11 +596,33 @@ def regex_fallback_conversion(sql: str) -> str: ) # IF(cond, a, b) -> CASE WHEN cond THEN a ELSE b END - sql = re.sub( - r'\bIF\s*\(\s*([^,]+),\s*([^,]+),\s*([^)]+)\)', - r'CASE WHEN \1 THEN \2 ELSE \3 END', + # Use balanced paren parsing to handle nested expressions + def replace_if(args): + # Parse comma-separated args manually (can't use split because of nested parens) + parts = [] + depth = 0 + current = [] + for char in args: + if char == ',' and depth == 0: + parts.append(''.join(current).strip()) + current = [] + else: + if char == '(': + depth += 1 + elif char == ')': + depth -= 1 + current.append(char) + if current: + parts.append(''.join(current).strip()) + + if len(parts) == 3: + return f'CASE WHEN {parts[0]} THEN {parts[1]} ELSE {parts[2]} END' + return f'IF({args})' # Fallback if parse fails + + sql = replace_function_with_balanced_parens( sql, - flags=re.IGNORECASE + r'\bIF\s*\(', + replace_if ) # IFNULL -> COALESCE diff --git a/grafana/scripts/entrypoint.sh b/grafana/scripts/entrypoint.sh new file mode 100644 index 00000000000..10e83f283c7 --- /dev/null +++ b/grafana/scripts/entrypoint.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +DATASOURCE_FILE="/etc/grafana/provisioning/datasources/datasource.yml" + +# Detect database type +# Priority: DATABASE_TYPE, then legacy MYSQL_*/POSTGRES_* auto-detection +if [ -n "$DATABASE_TYPE" ]; then + MODE="$DATABASE_TYPE" + + case "$MODE" in + mysql) + export MYSQL_URL="${DATABASE_HOST:-mysql}:${DATABASE_PORT:-3306}" + export MYSQL_DATABASE="${DATABASE_NAME:-lake}" + export MYSQL_USER="${DATABASE_USER:-merico}" + export MYSQL_PASSWORD="${DATABASE_PASSWORD:-merico}" + ;; + postgresql) + export POSTGRES_URL="${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}" + export POSTGRES_DATABASE="${DATABASE_NAME:-lake}" + export POSTGRES_USER="${DATABASE_USER:-merico}" + export POSTGRES_PASSWORD="${DATABASE_PASSWORD:-merico}" + ;; + *) + echo "ERROR: DATABASE_TYPE must be 'mysql' or 'postgresql'" + exit 1 + ;; + esac +else + # Legacy: auto-detect from MYSQL_*/POSTGRES_* vars + if [ -n "$POSTGRES_URL" ]; then + MODE="postgresql" + elif [ -n "$MYSQL_URL" ]; then + MODE="mysql" + else + echo "WARNING: No database vars. Defaulting to mysql." + MODE="mysql" + export MYSQL_URL="mysql:3306" + export MYSQL_DATABASE="lake" + export MYSQL_USER="merico" + export MYSQL_PASSWORD="merico" + fi +fi + +echo "Database type: $MODE" + +# Remove unused dashboard folder to prevent confusion +if [ "$MODE" = "mysql" ]; then + rm -rf /etc/grafana/dashboards/postgresql +else + rm -rf /etc/grafana/dashboards/mysql +fi + +# Generate datasource.yml +cat > "$DATASOURCE_FILE" << HEADER +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: 1 + +datasources: +HEADER + +if [ "$MODE" = "mysql" ]; then + cat >> "$DATASOURCE_FILE" << MYSQL_DS + - name: mysql + type: mysql + url: ${MYSQL_URL} + database: ${MYSQL_DATABASE} + user: ${MYSQL_USER} + secureJsonData: + password: ${MYSQL_PASSWORD} + editable: false +MYSQL_DS + export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/mysql/Homepage.json" +else + cat >> "$DATASOURCE_FILE" << POSTGRES_DS + - name: postgresql + type: postgres + url: ${POSTGRES_URL} + database: ${POSTGRES_DATABASE} + user: ${POSTGRES_USER} + secureJsonData: + password: ${POSTGRES_PASSWORD} + editable: false + jsonData: + sslmode: disable + postgresVersion: 1400 +POSTGRES_DS + export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/postgresql/Homepage.json" +fi + +echo "Homepage: $GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH" + +exec /run.sh "$@" diff --git a/grafana/scripts/requirements.txt b/grafana/scripts/requirements.txt new file mode 100644 index 00000000000..b7cfbb4ecec --- /dev/null +++ b/grafana/scripts/requirements.txt @@ -0,0 +1 @@ +sqlglot==25.29.0 From c7fd747eeb21eb51557664203e17a27189e8da68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:55:38 -0300 Subject: [PATCH 03/11] fix(grafana): improve mysql to postgresql conversion script - Disable ambiguous alias pattern that caused _computed suffix duplication - Add MySQL FIELD() function to PostgreSQL CASE WHEN conversion - Fix adoption_pct and deploy_count column name issues --- .../scripts/convert-mysql-to-postgresql.py | 812 +++++++++++++++--- 1 file changed, 704 insertions(+), 108 deletions(-) diff --git a/grafana/scripts/convert-mysql-to-postgresql.py b/grafana/scripts/convert-mysql-to-postgresql.py index 2c6db19988e..78624fe8390 100755 --- a/grafana/scripts/convert-mysql-to-postgresql.py +++ b/grafana/scripts/convert-mysql-to-postgresql.py @@ -8,7 +8,7 @@ import sys import re from pathlib import Path -from typing import Any, Dict +from typing import Any try: import sqlglot @@ -17,6 +17,26 @@ sys.exit(1) +def extract_balanced_parens(text: str, start_pos: int) -> tuple[str, int] | None: + """ + Extract content from start_pos to matching closing paren with balanced counting. + Returns (extracted_content, closing_paren_position) or None if no match. + """ + paren_depth = 0 + i = start_pos + + while i < len(text): + if text[i] == '(': + paren_depth += 1 + elif text[i] == ')': + if paren_depth == 0: + return (text[start_pos:i], i) + paren_depth -= 1 + i += 1 + + return None + + def convert_sql_mysql_to_postgres(sql: str) -> str: """ Convert MySQL SQL to PostgreSQL using sqlglot AST parsing. @@ -42,26 +62,96 @@ def convert_sql_mysql_to_postgres(sql: str) -> str: # Restore Grafana macros to lowercase (sqlglot uppercases them) converted = restore_grafana_macros(converted) + # Fix $__timeFilter(MAX/MIN(...)) macro expansion issue (Step 5) + # Must run AFTER restore_grafana_macros() to match lowercase $__timeFilter + converted = fix_timefilter_aggregate_macro(converted) + # Restore Grafana template variables converted = restore_grafana_variables(converted, placeholders) + # Convert $interval(expr) pattern - Grafana variable used as function name + # When $interval = 'DAYOFMONTH', becomes DAYOFMONTH(expr) + # When $interval = 'WEEKDAY', becomes WEEKDAY(expr) + # Replace with CASE statement for PostgreSQL + pattern = re.compile(r'\$interval\s*\(', re.IGNORECASE) + result_interval = [] + pos_interval = 0 + + for match in pattern.finditer(converted): + result_interval.append(converted[pos_interval:match.start()]) + extracted = extract_balanced_parens(converted, match.end()) + if extracted: + expr, closing_pos = extracted + replacement = f"CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM {expr}) ELSE (EXTRACT(ISODOW FROM {expr}) - 1) END" + result_interval.append(replacement) + pos_interval = closing_pos + 1 + else: + result_interval.append(match.group(0)) + pos_interval = match.end() + + result_interval.append(converted[pos_interval:]) + converted = ''.join(result_interval) + # Restore string literals converted = restore_string_literals(converted, string_literals) - # Convert MySQL date format patterns to PostgreSQL in TO_CHAR (after string restoration) - # MySQL uses %Y-%m, PostgreSQL uses YYYY-MM - converted = re.sub( - r"TO_CHAR\s*\(\s*([^,]+?)\s*,\s*'%Y-%m'\s*\)", - r"TO_CHAR(\1, 'YYYY-MM')", - converted, - flags=re.IGNORECASE - ) - converted = re.sub( - r"TO_CHAR\s*\(\s*([^,]+?)\s*,\s*'%y/%m'\s*\)", - r"TO_CHAR(\1, 'YY/MM')", - converted, - flags=re.IGNORECASE - ) + # Convert MySQL date format patterns to PostgreSQL (Step 1 - after string restoration) + converted = convert_mysql_date_formats(converted) + + # Convert INTERVAL 'n DAY' * WEEKDAY(expr) -> (EXTRACT(ISODOW FROM expr) - 1) * INTERVAL 'n day' + # Also handle negative case: INTERVAL 'n DAY' * -WEEKDAY(expr) -> -(EXTRACT(ISODOW FROM expr) - 1) * INTERVAL 'n day' + # This pattern appears after sqlglot converts DATE_SUB(x, INTERVAL WEEKDAY(y) DAY) + # Manual string building to handle nested parentheses properly + pattern = re.compile(r"INTERVAL\s+'(\d+)\s+DAY'\s*\*\s*(-?)\s*WEEKDAY\s*\(", re.IGNORECASE) + result = [] + pos = 0 + + for match in pattern.finditer(converted): + interval_num = match.group(1) + negative_sign = match.group(2) + result.append(converted[pos:match.start()]) + + extracted = extract_balanced_parens(converted, match.end()) + if extracted: + expr, closing_pos = extracted + if negative_sign: + replacement = f"{negative_sign}(EXTRACT(ISODOW FROM {expr}) - 1) * INTERVAL '{interval_num} day'" + else: + replacement = f"(EXTRACT(ISODOW FROM {expr}) - 1) * INTERVAL '{interval_num} day'" + result.append(replacement) + pos = closing_pos + 1 + else: + result.append(match.group(0)) + pos = match.end() + + # Add remaining text after last match + result.append(converted[pos:]) + converted = ''.join(result) + + # Convert standalone WEEKDAY(expr) calls (not multiplication pattern) + # WEEKDAY(date) returns 0-6 (Mon-Sun) in MySQL, ISODOW returns 1-7 (Mon-Sun) in PostgreSQL + # Pattern: WEEKDAY(...) BETWEEN/=//>=/<= + # Replace with: (EXTRACT(ISODOW FROM ...) - 1) to maintain 0-6 range + pattern2 = re.compile(r"\bWEEKDAY\s*\(", re.IGNORECASE) + result2 = [] + pos2 = 0 + + for match in pattern2.finditer(converted): + result2.append(converted[pos2:match.start()]) + + extracted = extract_balanced_parens(converted, match.end()) + if extracted: + expr, closing_pos = extracted + replacement = f"(EXTRACT(ISODOW FROM {expr}) - 1)" + result2.append(replacement) + pos2 = closing_pos + 1 + else: + result2.append(match.group(0)) + pos2 = match.end() + + # Add remaining text after last match + result2.append(converted[pos2:]) + converted = ''.join(result2) # DIV(TO_CHAR(date, 'YYYY-MM'), N) -> calculate month-based division (after format conversion) # Used for bucketing dates into N-month periods (e.g., half-years with N=6) @@ -74,7 +164,31 @@ def convert_sql_mysql_to_postgres(sql: str) -> str: # Convert AS 'alias' to AS "alias" after string restoration # PostgreSQL doesn't accept single quotes for column aliases - converted = re.sub(r'\bAS\s+\'([^\']+)\'', r'AS "\1"', converted, flags=re.IGNORECASE) + # Skip SQL type keywords (DATE, TIMESTAMP, etc.) - they should remain unquoted + def convert_single_quoted_alias(match): + alias = match.group(1) + alias_upper = alias.upper() + sql_type_keywords = {'DATE', 'TIME', 'TIMESTAMP', 'INT', 'INTEGER', 'BIGINT', 'SMALLINT', + 'DECIMAL', 'NUMERIC', 'TEXT', 'VARCHAR', 'CHAR', 'BOOLEAN', 'BOOL', + 'REAL', 'DOUBLE', 'FLOAT'} + # Only treat as type keyword if exact uppercase match (e.g., 'TIMESTAMP' not 'Timestamp') + if alias_upper in sql_type_keywords and alias == alias_upper: + return f'AS {alias}' # Unquote type keyword + return f'AS "{alias}"' # Quote regular alias + + converted = re.sub(r'\bAS\s+\'([^\']+)\'', convert_single_quoted_alias, converted, flags=re.IGNORECASE) + + # Quote bare (unquoted) column aliases to ensure case-sensitive matching + # sqlglot inconsistently quotes aliases: subquery has "AS Date", outer query has "\"Date\"" + # PostgreSQL folds unquoted identifiers to lowercase, causing column not found errors + # Pattern: AS (not already quoted, not a SQL type) + # Exclude SQL types: DATE, TIME, TIMESTAMP, INT, INTEGER, BIGINT, TEXT, VARCHAR, etc. + sql_types = r'(?!(?:DATE|TIME|TIMESTAMP|INT|INTEGER|BIGINT|SMALLINT|DECIMAL|NUMERIC|TEXT|VARCHAR|CHAR|BOOLEAN|BOOL|REAL|DOUBLE|FLOAT)\b)' + converted = re.sub( + rf'(? str: converted = re.sub(r'(=|<>|!=)\s+"([^"]+)"', r"\1 '\2'", converted) converted = re.sub(r'\bIN\s*\(\s*"([^"]+)"\s*\)', r"IN ('\1')", converted, flags=re.IGNORECASE) + # Convert CASE THEN/ELSE double-quoted strings to single quotes + # THEN "text" → THEN 'text', ELSE "text" → ELSE 'text' + converted = re.sub(r'\bTHEN\s+"([^"]+)"', r"THEN '\1'", converted, flags=re.IGNORECASE) + converted = re.sub(r'\bELSE\s+"([^"]+)"', r"ELSE '\1'", converted, flags=re.IGNORECASE) + + # Convert function argument double-quoted strings to single quotes + # Common in SPLIT_PART, CONCAT, etc.: func(..., "delimiter", ...) → func(..., 'delimiter', ...) + # Match: comma followed by whitespace, double-quoted string, optional whitespace, comma or closing paren + converted = re.sub(r',(\s*)"([^"]+)"(\s*[,)])', r",\1'\2'\3", converted) + + # Convert MySQL FIELD() function to PostgreSQL CASE WHEN + # MySQL: ORDER BY FIELD(column, 'val1', 'val2', 'val3') + # PostgreSQL: ORDER BY CASE column WHEN 'val1' THEN 1 WHEN 'val2' THEN 2 WHEN 'val3' THEN 3 END + def convert_field_function(match): + column = match.group(1).strip() + values_str = match.group(2).strip() + # Split values by comma, handle quoted strings + values = [v.strip() for v in re.findall(r"'[^']*'|[^,]+", values_str)] + + when_clauses = [] + for i, value in enumerate(values, start=1): + when_clauses.append(f"WHEN {value} THEN {i}") + + return f"CASE {column} {' '.join(when_clauses)} END" + + converted = re.sub( + r'\bFIELD\s*\(\s*([^,]+)\s*,\s*([^)]+)\s*\)', + convert_field_function, + converted, + flags=re.IGNORECASE + ) + # Convert double-quoted literals inside CONCAT to single quotes # CONCAT(..., "(text)", ...) → CONCAT(..., '(text)', ...) def replace_concat_strings(match): @@ -103,11 +249,297 @@ def replace_concat_strings(match): # THEN "text" → THEN 'text', ELSE "text" → ELSE 'text' converted = re.sub(r'\b(THEN|ELSE)\s+"([^"]+)"', r"\1 '\2'", converted, flags=re.IGNORECASE) + # Convert SELECT "string literal" AS to SELECT 'string literal' AS + # Only convert if string contains spaces (indicates literal not identifier) + converted = re.sub(r'\bSELECT\s+"([^"]*\s[^"]*)"(\s+AS\s+)', r"SELECT '\1'\2", converted, flags=re.IGNORECASE) + + # Convert concatenation "string literal" to 'string literal' + # Pattern: || "text" becomes || 'text' + converted = re.sub(r'\|\|\s*"([^"]+)"', r"|| '\1'", converted) + + # Fix EXISTS(SELECT COUNT(...)) = 0/1 patterns that cause boolean = integer error + # MySQL: EXISTS(SELECT COUNT(...)) = 0 returns true/false, PostgreSQL: EXISTS returns boolean, can't compare to int + # Pattern 1: EXISTS(SELECT COUNT(...) FROM ...) = 0 → NOT EXISTS(SELECT 1 FROM ...) + converted = re.sub( + r'EXISTS\s*\(\s*SELECT\s+COUNT\s*\([^)]*\)\s+FROM\s+([^)]+)\)\s*=\s*0', + r'NOT EXISTS(SELECT 1 FROM \1)', + converted, + flags=re.IGNORECASE + ) + # Pattern 2: EXISTS(SELECT COUNT(...) FROM ...) = 1 → EXISTS(SELECT 1 FROM ...) + converted = re.sub( + r'EXISTS\s*\(\s*SELECT\s+COUNT\s*\([^)]*\)\s+FROM\s+([^)]+)\)\s*=\s*1', + r'EXISTS(SELECT 1 FROM \1)', + converted, + flags=re.IGNORECASE + ) + + # Ambiguous alias fix DISABLED - causes incorrect _computed suffix duplication + # The pattern was too greedy and matched cases where aliases are reused across CTEs + # Example issue: adoption_pct → adoption_pct_computed → adoption_pct_computed_computed + # If specific ambiguous column errors appear, handle them case-by-case + + # Fix mixed-type CASE expressions (TEXT and NUMERIC) + # PostgreSQL: all CASE branches must be same type + # Pattern: CASE with THEN 'string' and THEN numeric/arithmetic + # Solution: Cast arithmetic branches to ::TEXT + # Example: THEN value/60 ELSE 'No data' → THEN (value/60)::TEXT ELSE 'No data' + def cast_numeric_then_clause(match): + # Match captures: group(1) = THEN expression, group(2) = ELSE string + then_expr = match.group(1).strip() + else_str = match.group(2) + # If expression contains division or CAST/NULLIF (likely numeric), wrap in ()::TEXT + if '/' in then_expr or 'CAST' in then_expr.upper() or 'NULLIF' in then_expr.upper(): + # Wrap in parens if not already wrapped + if not (then_expr.startswith('(') and then_expr.endswith(')')): + then_expr = f'({then_expr})' + return f'THEN {then_expr}::TEXT ELSE \'{else_str}\'' + return match.group(0) + + # Match: THEN ELSE 'string_literal' + # Negative lookahead (?!.*\bWHEN\b) ensures we don't match across WHEN boundaries + converted = re.sub( + r'\bTHEN\s+((?!.*\bWHEN\b).+?)\s+ELSE\s+\'([^\']+)\'', + cast_numeric_then_clause, + converted, + flags=re.IGNORECASE | re.DOTALL + ) + + # Convert SUBSTRING_INDEX to PostgreSQL equivalent + # MySQL: SUBSTRING_INDEX(str, delim, count) - get substring before/after delimiter occurrence + # PostgreSQL: SPLIT_PART for positive count, REVERSE+SPLIT_PART for negative count + def convert_substring_index(match): + str_arg = match.group(1).strip() + delim_arg = match.group(2).strip() + count_arg = match.group(3).strip() + + # Check if count is negative (get from end) + if count_arg.startswith('-'): + # Negative count: get from end + # SUBSTRING_INDEX(str, delim, -1) → REVERSE(SPLIT_PART(REVERSE(str), delim, 1)) + positive_count = count_arg[1:] # Remove minus sign + return f'REVERSE(SPLIT_PART(REVERSE({str_arg}), {delim_arg}, {positive_count}))' + else: + # Positive count: get from beginning + # SUBSTRING_INDEX(str, delim, 1) → SPLIT_PART(str, delim, 1) + return f'SPLIT_PART({str_arg}, {delim_arg}, {count_arg})' + + # Match: SUBSTRING_INDEX(str, delim, count) + converted = re.sub( + r'\bSUBSTRING_INDEX\s*\(\s*([^,]+),\s*([^,]+),\s*([^)]+)\)', + convert_substring_index, + converted, + flags=re.IGNORECASE + ) + + # Fix STRING_AGG double-quoted separator (from sqlglot GROUP_CONCAT conversion) + # PostgreSQL: STRING_AGG(expr, "sep") → STRING_AGG(expr, 'sep') + # sqlglot converts GROUP_CONCAT but keeps separator quoting from source + # Use greedy match to handle expressions with internal commas + converted = re.sub( + r'\bSTRING_AGG\s*\((.+),\s*"([^"]*)"\s*\)', + r"STRING_AGG(\1, '\2')", + converted, + flags=re.IGNORECASE + ) + + # Fix HAVING clause with column alias - PostgreSQL doesn't allow aliases in HAVING + # MySQL: HAVING `alias` IS NOT NULL + # PostgreSQL: Remove HAVING on aggregate NULL checks (naturally filtered) + # Pattern: HAVING NOT "alias" IS NULL or HAVING "alias" IS NOT NULL + converted = re.sub( + r'\bHAVING\s+(?:NOT\s+)?"([^"]+)"\s+IS\s+(?:NOT\s+)?NULL', + '', + converted, + flags=re.IGNORECASE + ) + + # Fix ambiguous identifier in GROUP BY when alias matches column name + # PostgreSQL folds unquoted identifiers to lowercase, causing ambiguity + # Quote all bare (unquoted, non-numeric) identifiers in GROUP BY + def quote_group_by_identifiers(sql_text): + def quote_group_by_item(match): + prefix = match.group(1) # "GROUP BY " + items_str = match.group(2) # rest of clause + + # Split by comma to handle multiple items + items = [] + for item in items_str.split(','): + item = item.strip() + # Skip if already quoted, numeric positional, or empty + if not item or item.startswith('"') or item.isdigit(): + items.append(item) + # Check if it's a word (identifier) - quote it + elif re.match(r'^\w+$', item): + items.append(f'"{item}"') + else: + # Complex expression (table.col, etc.) - keep as-is + items.append(item) + + return prefix + ', '.join(items) + + # Match GROUP BY clause (from GROUP BY to next keyword or end) + sql_text = re.sub( + r'\b(GROUP\s+BY\s+)([^;]*?)(?=\s*(?:HAVING|ORDER|LIMIT|$))', + quote_group_by_item, + sql_text, + flags=re.IGNORECASE + ) + return sql_text + + converted = quote_group_by_identifiers(converted) + + # Quote capitalized identifiers in ORDER BY (same issue as GROUP BY) + # Pattern: ORDER BY - quote to match case-sensitive alias + def quote_order_by_identifiers(sql_text): + def quote_order_by_item(match): + prefix = match.group(1) # "ORDER BY " + items_str = match.group(2) # rest before DESC/ASC/NULLS/end + + # Split by comma + items = [] + for item in items_str.split(','): + item = item.strip() + # Match: bare capitalized word (not quoted, not numeric) + word_match = re.match(r'^([A-Z]\w*)$', item) + if word_match and not item.isdigit(): + items.append(f'"{item}"') + elif item and not item.startswith('"'): + items.append(item) + else: + items.append(item) + + return prefix + ', '.join(items) + + # Match ORDER BY clause up to DESC/ASC/NULLS or next keyword + sql_text = re.sub( + r'\b(ORDER\s+BY\s+)([^;]+?)(?=\s+(?:DESC|ASC|NULLS|LIMIT|$))', + quote_order_by_item, + sql_text, + flags=re.IGNORECASE + ) + return sql_text + + converted = quote_order_by_identifiers(converted) + + # Quote capitalized bare identifiers in SELECT column lists + # Pattern: SELECT ..., Capitalized, ... (not after AS, not SQL keywords) + # Must match column references that correspond to quoted aliases from subqueries + def quote_select_columns(sql_text): + # Match SELECT clause, extract column list + def quote_columns_in_select(match): + select_keyword = match.group(1) # "SELECT " or "SELECT DISTINCT " etc + columns_clause = match.group(2) # column list + + # Split by comma, quote capitalized bare identifiers + columns = [] + for col in columns_clause.split(','): + col = col.strip() + # Skip if already quoted, contains operators/functions, or is * + if col.startswith('"') or col == '*' or '(' in col or '.' in col or 'AS ' in col.upper(): + columns.append(col) + # Quote capitalized bare word (e.g., Activity, Details, Name) + elif re.match(r'^[A-Z]\w*$', col): + columns.append(f'"{col}"') + else: + columns.append(col) + + return select_keyword + ', '.join(columns) + + # Match SELECT column list (from SELECT to FROM) + sql_text = re.sub( + r'\b(SELECT(?:\s+DISTINCT)?\s+)(.*?)(?=\s+FROM\b)', + quote_columns_in_select, + sql_text, + flags=re.IGNORECASE + ) + return sql_text + + converted = quote_select_columns(converted) + + # Quote table.Column references where Column is capitalized + # Pattern: table.Date → table."Date" (prevents case-folding in JOIN/WHERE clauses) + # Match: word.Capitalized (not followed by opening paren for functions) + converted = re.sub(r'\.([A-Z]\w*)\b(?!\s*\()', r'."\1"', converted) + + # Quote bare capitalized identifiers in CASE WHEN clauses + # WHEN Activity = 'value' → WHEN "Activity" = 'value' + # Match WHEN case-insensitive, but identifier must start with uppercase + converted = re.sub( + r'\b(?:WHEN|when)\s+([A-Z]\w*)\s*(=|<>|!=|>|<|>=|<=|IN|LIKE|IS)', + r'WHEN "\1" \2', + converted + ) + + # Quote bare capitalized identifiers in WHERE clauses + # WHERE Date > '2020' → WHERE "Date" > '2020' + # Match WHERE case-insensitive, but identifier must start with uppercase + converted = re.sub( + r'\b(?:WHERE|where)\s+([A-Z]\w*)\s*(=|<>|!=|>|<|>=|<=|IN|LIKE|IS)', + r'WHERE "\1" \2', + converted + ) + + # Fix text column comparisons to integers + # MySQL allows implicit conversion, PostgreSQL doesn't + # issue_key <> 0 → issue_key <> '0' (text column needs text literal) + converted = re.sub( + r'\b([a-z_]+)\s*(=|<>|!=)\s*0\b', + r"\1 \2 '0'", + converted + ) + + # Protect equality comparisons with Grafana variables from empty values + # connection_id = ${var} → ('${var}' = '' OR connection_id::text = '${var}') + # table.connection_id = ${var} → ('${var}' = '' OR table.connection_id::text = '${var}') + # When variable empty, condition becomes TRUE (no filter), when set, applies filter + # Cast both sides to text to handle integer/text columns uniformly + def protect_equality_variable(match): + column = match.group(1) + operator = match.group(2) + var = match.group(3) + return f"('{var}' = '' OR {column}::text {operator} '{var}')" + + # Match: column = ${var} or table.column = ${var} + # Capture optional table prefix with (?:\.\w+)? + converted = re.sub( + r'(\w+(?:\.\w+)?)\s*(=|<>|!=)\s*(\$\{[^}]+\}|\$[a-z_][a-z0-9_]*)', + protect_equality_variable, + converted + ) + + # PostgreSQL rejects IN () when Grafana variables are empty - wrap with CASE/unnest + # Also cast column to text to avoid type mismatch (bigint = text) + def protect_in_clause_with_cast(match): + column = match.group(1) + var = match.group(2) + # Cast column to text if not already cast + if '::' not in column: + column = f'{column}::text' + return f"{column} IN (SELECT v FROM unnest(CASE WHEN '{var}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[{var}]::text[] END) v)" + + converted = re.sub( + r'(\S+)\s+IN\s*\(\s*(\$\{[^}]+\})\s*\)', # column IN (${var}) + protect_in_clause_with_cast, + converted, + flags=re.IGNORECASE + ) + + converted = re.sub( + r'(\S+)\s+IN\s*\(\s*(\$(?!__)[a-z_][a-z0-9_]*)\s*\)', # column IN ($var) + protect_in_clause_with_cast, + converted, + flags=re.IGNORECASE + ) + return converted except Exception as e: # Fallback to regex-based conversion for complex cases print(f" [WARN] sqlglot failed, using regex fallback: {str(e)[:100]}") - return regex_fallback_conversion(sql) + fallback = regex_fallback_conversion(sql) + fallback = restore_grafana_variables(fallback, placeholders) + fallback = restore_string_literals(fallback, string_literals) + return fallback def protect_string_literals(sql_text: str) -> tuple[str, dict]: @@ -182,6 +614,131 @@ def restore_string_literals(sql_text: str, literals: dict) -> str: return sql_text +def convert_mysql_date_formats(sql: str) -> str: + """ + Convert MySQL date format specifiers to PostgreSQL in TO_CHAR/TO_DATE calls. + Operates character-by-character within format strings. + Step 1 of the conversion fixes. + """ + # MySQL -> PostgreSQL format mapping + format_map = { + '%Y': 'YYYY', # 4-digit year + '%y': 'YY', # 2-digit year + '%X': 'IYYY', # ISO year + '%V': 'IW', # ISO week + '%m': 'MM', # month number + '%M': 'FMMonth', # full month name + '%d': 'DD', # day of month + '%W': 'FMDay', # full weekday name + '%w': 'D', # day of week number + '%u': 'IW', # ISO week + '%H': 'HH24', # 24-hour + '%i': 'MI', # minutes + '%s': 'SS' # seconds + } + + def convert_format_string(format_str): + """Convert a single format string from MySQL to PostgreSQL.""" + # Skip if already PostgreSQL format (contains YYYY, MM, DD without %) + if not '%' in format_str and any(pg in format_str for pg in ['YYYY', 'MM', 'DD', 'HH24']): + return format_str + + result = [] + i = 0 + while i < len(format_str): + if format_str[i] == '%' and i + 1 < len(format_str): + # Try to match %X format specifier + two_char = format_str[i:i+2] + if two_char in format_map: + result.append(format_map[two_char]) + i += 2 + else: + # Unknown specifier, keep as-is + result.append(format_str[i]) + i += 1 + else: + result.append(format_str[i]) + i += 1 + return ''.join(result) + + # Find and convert TO_CHAR(..., 'format') and TO_DATE(..., 'format') + def replacer(match): + func_name = match.group(1) + first_arg = match.group(2) + format_str = match.group(3) + converted_format = convert_format_string(format_str) + return f"{func_name}({first_arg}, '{converted_format}')" + + # Pattern matches TO_CHAR(expr, 'format') or TO_DATE(expr, 'format') + # Use non-greedy matching for first argument + sql = re.sub( + r'\b(TO_CHAR|TO_DATE)\s*\(\s*([^,]+?)\s*,\s*\'([^\']+)\'\s*\)', + replacer, + sql, + flags=re.IGNORECASE + ) + + return sql + + +def fix_timefilter_aggregate_macro(sql: str) -> str: + """ + Fix $__timeFilter(MAX/MIN(...)) macro expansion that creates MAX(boolean). + Converts: $__timeFilter(MAX(col)) -> MAX(col) BETWEEN $__timeFrom() AND $__timeTo() + Step 5 of the conversion fixes. + Must run AFTER restore_grafana_macros() to match lowercase $__timeFilter. + """ + # Simple pattern for $__timeFilter(MAX(col)) or $__timeFilter(MIN(col)) + # Handles simple column references + sql = re.sub( + r'\$__timeFilter\s*\(\s*(MAX|MIN)\s*\(([^)]+)\)\s*\)', + r'\1(\2) BETWEEN $__timeFrom() AND $__timeTo()', + sql, + flags=re.IGNORECASE + ) + + # For nested expressions like $__timeFilter(MAX(CAST(col AS DATE))), + # use balanced paren matching + pattern = r'\$__timeFilter\s*\(' + result = [] + i = 0 + while i < len(sql): + match = re.match(pattern, sql[i:], re.IGNORECASE) + if match: + start = i + match.end() + # Check if next token is MAX or MIN + agg_match = re.match(r'\s*(MAX|MIN)\s*\(', sql[start:], re.IGNORECASE) + if agg_match: + agg_func = agg_match.group(1).upper() + agg_start = start + agg_match.end() + # Find matching closing paren for MAX/MIN + depth = 1 + j = agg_start + while j < len(sql) and depth > 0: + if sql[j] == '(': + depth += 1 + elif sql[j] == ')': + depth -= 1 + j += 1 + if depth == 0: + # Found closing paren for MAX/MIN + agg_arg = sql[agg_start:j-1] + # Now find closing paren for $__timeFilter + if j < len(sql) and sql[j] == ')': + # Replace with aggregate BETWEEN pattern + result.append(f"{agg_func}({agg_arg}) BETWEEN $__timeFrom() AND $__timeTo()") + i = j + 1 + continue + # Not a MAX/MIN pattern, keep original + result.append(sql[i]) + i += 1 + else: + result.append(sql[i]) + i += 1 + + return ''.join(result) + + def replace_function_with_balanced_parens(sql_text: str, func_pattern: str, replacement_func) -> str: """ Generic function replacer that handles nested parentheses via depth counting. @@ -287,6 +844,27 @@ def post_process_sql(sql: str) -> str: lambda arg: f'EXTRACT(HOUR FROM {arg})' ) + # DAYOFMONTH() -> EXTRACT(DAY FROM ...) (no underscore variant) + sql = replace_function_with_balanced_parens( + sql, + r'\bDAYOFMONTH\s*\(', + lambda arg: f'EXTRACT(DAY FROM {arg})' + ) + + # DAY_OF_MONTH() -> EXTRACT(DAY FROM ...) (with underscore variant) + sql = replace_function_with_balanced_parens( + sql, + r'\bDAY_OF_MONTH\s*\(', + lambda arg: f'EXTRACT(DAY FROM {arg})' + ) + + # DAY() -> EXTRACT(DAY FROM ...) + sql = replace_function_with_balanced_parens( + sql, + r'\bDAY\s*\(', + lambda arg: f'EXTRACT(DAY FROM {arg})' + ) + # Boolean comparisons: column = 1 → column = TRUE, column = 0 → column = FALSE # PostgreSQL schema has BOOLEAN type - does NOT accept integer comparison # ERROR: "operator does not exist: boolean = integer" @@ -340,62 +918,19 @@ def post_process_sql(sql: str) -> str: flags=re.IGNORECASE ) + # NUMBER_TO_STR -> ROUND + cast to text + # MySQL custom: NUMBER_TO_STR(value, decimals) + # PostgreSQL: ROUND(value, decimals)::TEXT + sql = replace_function_with_balanced_parens( + sql, + r'\bNUMBER_TO_STR\s*\(', + lambda arg: f'ROUND({arg})::TEXT' + ) + # FIND_IN_SET -> ANY(string_to_array()) # MySQL: FIND_IN_SET(value, csv_string) > 0 # PostgreSQL: value = ANY(string_to_array(csv_string, ',')) - # Handle nested functions by matching balanced parentheses - def replace_find_in_set(sql): - pattern = r'FIND_IN_SET\s*\(' - result = [] - i = 0 - while i < len(sql): - match = re.match(pattern, sql[i:], re.IGNORECASE) - if match: - start = i + match.end() - # Find matching closing paren and split args - depth = 1 - arg1_end = None - for j in range(start, len(sql)): - if sql[j] == '(': - depth += 1 - elif sql[j] == ')': - depth -= 1 - if depth == 0: - # Found closing paren, now find the comma separator - for k in range(start, j): - if sql[k] == ',' and depth == 1: - # Count depth properly - temp_depth = 0 - valid_comma = True - for m in range(start, k): - if sql[m] == '(': - temp_depth += 1 - elif sql[m] == ')': - temp_depth -= 1 - if temp_depth == 0: - arg1_end = k - break - if arg1_end: - arg1 = sql[start:arg1_end].strip() - arg2 = sql[arg1_end+1:j].strip() - # Check if followed by > 0 - rest_start = j + 1 - gt_match = re.match(r'\s*>\s*0', sql[rest_start:]) - if gt_match: - result.append(f"{arg1} = ANY(string_to_array({arg2}, ','))") - i = rest_start + gt_match.end() - continue - break - elif sql[j] == ',' and depth == 1: - arg1_end = j - result.append(sql[i]) - i += 1 - else: - result.append(sql[i]) - i += 1 - return ''.join(result) - - # Simpler approach: iteratively replace innermost FIND_IN_SET first + # Iteratively replace innermost FIND_IN_SET first prev_sql = None while prev_sql != sql: prev_sql = sql @@ -435,13 +970,6 @@ def replace_find_in_set(sql): lambda arg: f'EXTRACT(EPOCH FROM {arg})' ) - # GROUP_CONCAT -> STRING_AGG - sql = replace_function_with_balanced_parens( - sql, - r'\bGROUP_CONCAT\s*\(', - lambda arg: f"STRING_AGG({arg}, ',')" - ) - # TIMESTAMPDIFF - handle sqlglot format: TIMESTAMPDIFF(end, start, unit) # PostgreSQL needs: EXTRACT(EPOCH FROM (end - start)) / divisor # Use generic function to handle nested parens in arguments @@ -482,21 +1010,33 @@ def replace_timestampdiff(args): replace_timestampdiff ) + # Step 3: Fix MySQL-specific type casts + sql = re.sub(r'\bUBIGINT\b', 'BIGINT', sql, flags=re.IGNORECASE) + sql = re.sub(r'\bAS\s+UNSIGNED\b', 'AS BIGINT', sql, flags=re.IGNORECASE) + sql = re.sub(r'\bAS\s+SIGNED\b', 'AS BIGINT', sql, flags=re.IGNORECASE) + + # Step 4: YEARWEEK() conversion + # YEARWEEK(col, 1) -> (EXTRACT(ISOYEAR FROM col) * 100 + EXTRACT(WEEK FROM col))::int + sql = re.sub( + r'\bYEARWEEK\s*\(\s*([^,]+?)\s*,\s*\d+\s*\)', + r'(EXTRACT(ISOYEAR FROM \1) * 100 + EXTRACT(WEEK FROM \1))::int', + sql, + flags=re.IGNORECASE + ) + return sql def protect_grafana_variables(sql: str) -> tuple: """ Replace Grafana template variables with safe placeholders before sqlglot. - sqlglot treats ${var} as MySQL user variables and converts to STRUCT(var). + sqlglot uppercases bare variables like $interval → $INTERVAL, breaking Grafana interpolation. + Protects both ${var} and $var (but not $__macros). Returns: (modified_sql, dict of placeholders) """ placeholders = {} counter = 0 - # Match Grafana variables: ${variable} or ${variable:format} - pattern = r'\$\{([^}]+)\}' - def replacer(match): nonlocal counter placeholder = f'GRAFANA_PLACEHOLDER_{counter}_GRAFANA' @@ -504,29 +1044,22 @@ def replacer(match): counter += 1 return placeholder - protected_sql = re.sub(pattern, replacer, sql) + # Protect ${variable} or ${variable:format} + protected_sql = re.sub(r'\$\{([^}]+)\}', replacer, sql) + + # Protect bare $variable (but not $__macros like $__timeFilter) + protected_sql = re.sub(r'\$(?!__)[a-zA-Z_][a-zA-Z0-9_]*', replacer, protected_sql) + return protected_sql, placeholders def restore_grafana_variables(sql: str, placeholders: dict) -> str: """ Restore Grafana template variables from safe placeholders. - Also convert IN (${var}) to = ANY(ARRAY[${var}]) to handle empty variables. - PostgreSQL rejects IN () with no values, but = ANY(ARRAY[]) is valid. """ for placeholder, original in placeholders.items(): sql = sql.replace(placeholder, original) - # Convert IN (${var}) to = ANY(ARRAY[${var}]::text[]) - # Empty: column = ANY(ARRAY[]::text[]) → FALSE (typed empty array) - # Values: column = ANY(ARRAY['val1','val2']::text[]) → works correctly - sql = re.sub( - r'(\w+(?:\.\w+)?)\s+IN\s+\(\$\{([^}]+)\}\)', - r'\1 = ANY(ARRAY[${\2}]::text[])', - sql, - flags=re.IGNORECASE - ) - return sql @@ -539,16 +1072,79 @@ def restore_grafana_macros(sql: str) -> str: sql = re.sub(r'\$__TIMEFROM', '$__timeFrom', sql) sql = re.sub(r'\$__TIMETO', '$__timeTo', sql) - # Fix WEEKDAY function conversion - # MySQL: DATE_SUB(DATE(x), INTERVAL WEEKDAY(DATE(x)) DAY) - # PostgreSQL: CAST(x AS DATE) - (EXTRACT(ISODOW FROM x) - 1) * INTERVAL '1 day' - # Pattern: CAST(x AS DATE) - INTERVAL 'WEEKDAY DAY' - sql = re.sub( - r'CAST\s*\(([^)]+)\s+AS\s+DATE\)\s*-\s*INTERVAL\s+[\'"]WEEKDAY\s+DAY[\'"]', - r"CAST(\1 AS DATE) - (EXTRACT(ISODOW FROM \1) - 1) * INTERVAL '1 day'", - sql, - flags=re.IGNORECASE - ) + # Step 2: Fix WEEKDAY/INTERVAL conversion (broadened pattern) + # Handles: day - INTERVAL 'WEEKDAY DAY', metric_date - INTERVAL 'WEEKDAY DAY', + # TO_TIMESTAMP(...) - INTERVAL 'WEEKDAY DAY', etc. + # Find INTERVAL 'WEEKDAY DAY' and walk backwards to find expression + pattern = r"INTERVAL\s+['\"]WEEKDAY\s+DAY['\"]" + result = [] + i = 0 + + while i < len(sql): + match = re.search(pattern, sql[i:], re.IGNORECASE) + if not match: + result.append(sql[i:]) + break + + # Found INTERVAL 'WEEKDAY DAY' at position i + match.start() + interval_start = i + match.start() + + # Walk backwards to find the subtraction operator and LHS expression + # Look for ' - ' before the INTERVAL + minus_pos = None + for j in range(interval_start - 1, max(0, interval_start - 50), -1): + if sql[j:j+3] == ' - ' or (j > 0 and sql[j-1:j+2] == ' - '): + minus_pos = j + break + + if minus_pos is not None: + # Walk backwards from minus to find start of expression + # Stop at keywords, commas, or opening parens at depth 0 + expr_start = 0 + depth = 0 + for j in range(minus_pos - 1, -1, -1): + if sql[j] == ')': + depth += 1 + elif sql[j] == '(': + depth -= 1 + elif depth == 0 and sql[j] in (',', '\n'): + expr_start = j + 1 + break + elif depth == 0 and j >= 8: + # Check for SQL keywords - need to look for GROUP BY specifically + upper_substr = sql[max(0, j-8):j+1].upper() + for kw in ['GROUP BY ', 'ORDER BY ', 'SELECT ', 'WHERE ', 'AND ', 'WHEN ', 'BETWEEN ']: + kw_pos = upper_substr.rfind(kw) + if kw_pos >= 0: + # Found keyword, expr starts after it + expr_start = max(0, j-8) + kw_pos + len(kw) + break + if expr_start > 0: + break + + # Extract the expression + expr = sql[expr_start:minus_pos].strip() + + # Append everything before expr_start + result.append(sql[i:expr_start]) + + # Build replacement + replacement = f"{expr} - (EXTRACT(ISODOW FROM {expr}) - 1) * INTERVAL '1 day'" + result.append(replacement) + + # Skip past the INTERVAL 'WEEKDAY DAY' + # match.end() is relative to sql[i:], so absolute position is i + match.end() + i = i + match.end() + else: + # Could not find minus operator, keep original + result.append(match.group(0)) + i = i + match.end() + + sql = ''.join(result) + + # Also handle: expr + INTERVAL '6 DAY' pattern (week-end calculation) + # Convert: expr - INTERVAL 'WEEKDAY DAY' + INTERVAL '6 DAY' + # But this is handled naturally by the above since we only replace the WEEKDAY part return sql @@ -662,6 +1258,9 @@ def process_dashboard_recursive(obj: Any, path: str = "") -> Any: # Convert rawSql fields if key == "rawSql" and isinstance(value, str): result[key] = convert_sql_mysql_to_postgres(value) + # Convert query and definition fields (dashboard variables) + elif key in ("query", "definition") and isinstance(value, str) and ("SELECT" in value.upper() or "CAST" in value.upper()): + result[key] = convert_sql_mysql_to_postgres(value) # Convert datasource string references elif key == "datasource" and isinstance(value, str) and value == "mysql": result[key] = "postgresql" @@ -698,10 +1297,7 @@ def convert_dashboard(input_path: Path, output_path: Path) -> None: if not converted["uid"].endswith("-pg"): converted["uid"] = f"{converted['uid']}-pg" - # Update title to indicate PostgreSQL variant (optional) - if "title" in converted: - if not "(PostgreSQL)" in converted["title"]: - converted["title"] = f"{converted['title']} (PostgreSQL)" + # Title kept as-is (no PostgreSQL suffix needed) # Write output dashboard output_path.parent.mkdir(parents=True, exist_ok=True) From aac260d51484698b0407e42f768031139c9d0a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:55:46 -0300 Subject: [PATCH 04/11] chore(grafana): remove archived dashboards Remove outdated dashboard files from _archive directory --- .../_archive/Daily_Needs_To_Be_Improved.json | 389 -- .../DataSourceSpecificDashboardsHomepage.json | 293 -- ...eliveryCapability(RequireJenkinsData).json | 470 -- .../DeliveryCost(RequireGitlabData).json | 408 -- ...veryQuality(RequireJiraAndGitlabData).json | 1699 ------- ...eryVelocity(RequireJiraAndGitlabData).json | 4093 ----------------- ...ReleaseQualityAndContributionAnalysis.json | 2764 ----------- grafana/_archive/GithubDomainLayerOnly.json | 622 --- grafana/_archive/Gitlab.json | 2186 --------- grafana/_archive/Jenkins.json | 470 -- .../UserValueSpecificDashboardsHomepage.json | 344 -- 11 files changed, 13738 deletions(-) delete mode 100644 grafana/_archive/Daily_Needs_To_Be_Improved.json delete mode 100644 grafana/_archive/DataSourceSpecificDashboardsHomepage.json delete mode 100644 grafana/_archive/DeliveryCapability(RequireJenkinsData).json delete mode 100644 grafana/_archive/DeliveryCost(RequireGitlabData).json delete mode 100644 grafana/_archive/DeliveryQuality(RequireJiraAndGitlabData).json delete mode 100644 grafana/_archive/DeliveryVelocity(RequireJiraAndGitlabData).json delete mode 100644 grafana/_archive/EeGitHubReleaseQualityAndContributionAnalysis.json delete mode 100644 grafana/_archive/GithubDomainLayerOnly.json delete mode 100644 grafana/_archive/Gitlab.json delete mode 100644 grafana/_archive/Jenkins.json delete mode 100644 grafana/_archive/UserValueSpecificDashboardsHomepage.json diff --git a/grafana/_archive/Daily_Needs_To_Be_Improved.json b/grafana/_archive/Daily_Needs_To_Be_Improved.json deleted file mode 100644 index 7cbeec9924a..00000000000 --- a/grafana/_archive/Daily_Needs_To_Be_Improved.json +++ /dev/null @@ -1,389 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "description": "", - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 32, - "iteration": 1661321745952, - "links": [], - "panels": [ - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 57, - "options": { - "content": "- Use Cases: This dashboard can be used to track bugs.\n- Data Source Required: GitHub ([transformation](https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub#step-3---adding-transformation-rules-optional) required. Additional settings are required to get version data).", - "mode": "markdown" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "title": "Dashboard Introduction", - "type": "text" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [], - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "label_name" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "semi-dark-green", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "count" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "palette-classic" - } - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 7, - "x": 0, - "y": 4 - }, - "id": 59, - "options": { - "displayLabels": [ - "name", - "percent" - ], - "legend": { - "displayMode": "hidden", - "placement": "right", - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "select \ndistinct il.label_name, count(il.label_name) as count\nfrom \nissues i\nleft join issue_labels il on i.id = il.issue_id\nwhere i.type = \"BUG\" and il.label_name like \"%O-%\" and i.updated_date > \"2021-08-01 00:00:00.000\"\ngroup by il.label_name\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "01. Bug Source", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "displayMode": "auto" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "=avg_bug_age" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "issue_key" - }, - "properties": [ - { - "id": "custom.width", - "value": 81 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "title" - }, - "properties": [ - { - "id": "custom.width", - "value": 535 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "created_date" - }, - "properties": [ - { - "id": "custom.width", - "value": 149 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "queue_time_in_days" - }, - "properties": [ - { - "id": "custom.width", - "value": 140 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "issue_url" - }, - "properties": [ - { - "id": "custom.width", - "value": 399 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "sha" - }, - "properties": [ - { - "id": "custom.width", - "value": 328 - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 17, - "x": 7, - "y": 4 - }, - "id": 61, - "options": { - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with issue_sha_deveq as(\nselect\ni.url as issue_url, c.sha, c.dev_eq\nfrom \npull_request_issues pri\nleft join pull_request_commits prc on prc.pull_request_id = pri.pull_request_id\nleft join issues i on pri.issue_id = i.id\nleft join commits c on c.sha = prc.commit_sha\nwhere i.type = \"BUG\" and i.status = \"DONE\" and c.sha != \"\" and c.committed_date > \"2021-08-01 00:00:00.000\"\n)\n\nselect\nissue_url, sum(dev_eq) as dev_eq_total\nfrom\nissue_sha_deveq\ngroup by issue_url \norder by dev_eq_total DESC", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "02. Bug Dev_eq", - "type": "table" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": null, - "definition": "select concat(name, '--', id) as text from repos", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Repo", - "multi": true, - "name": "repo_id", - "options": [], - "query": "select concat(name, '--', id) as text from repos", - "refresh": 1, - "regex": "/^(?.*)--(?.*)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Daily_Needs_To_Be_Improved", - "uid": "2xuOaQUnk5", - "version": 25 -} \ No newline at end of file diff --git a/grafana/_archive/DataSourceSpecificDashboardsHomepage.json b/grafana/_archive/DataSourceSpecificDashboardsHomepage.json deleted file mode 100644 index ce589d087b2..00000000000 --- a/grafana/_archive/DataSourceSpecificDashboardsHomepage.json +++ /dev/null @@ -1,293 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 12, - "links": [], - "panels": [ - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 0, - "y": 0 - }, - "id": 10, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"jira\"\n

Jira

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 7, - "y": 0 - }, - "id": 5, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"gitlab\"\n

Gitlab

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "datasource": null, - "gridPos": { - "h": 18, - "w": 10, - "x": 14, - "y": 0 - }, - "id": 9, - "links": [], - "options": { - "content": "![Merico Logo](/public/img/lake/logo.png \"Merico\")\n***\n# **MARI Introduction**\n***\nMARI is a methodology developed specifically to bring the process of data driven software engineering to organizations of all sizes in a simple and organized way; empowering them to translate measurement into insight, and insight into results. MARI is a cycle of iterations, focused on mapping challenges to metrics, metrics to actions, and actions to improvements. \n

\n\n\n#### *M (Measure)*\n \nGreat answers require great questions. To get started, identify specific and clearly definable pain points in your development processes or teams, set tightly constrained goals, and then with team consensus, prioritize those goals. From here, identify 'North Star' metrics and measurements that map to each goal. Once you have clearly defined and documented the pain-points, mapped them to metrics, and prioritized, it's time to standardize these metrics and methods across teams. \n\n##### *A (Analyze)*\n\nEffective measures are only as good as the analysis that gets applied to them. To understand productivity in an effective and quantitative way, look towards key attributes like trend, distribution, variability, dispersion, and periodicity. These analysis techniques can uncover problems and opportunities. \n\n##### *R (Review)*\n\nOnce you have equipped yourself with the results of the analysis, it's time to coherently review them and discover what hinders your team from reaching full potential. With key stakeholders in the conversation, assess your workflow, toolset, developer satisfaction and many other aspects with an eye towards identifying cause and effect. It is critical that this part of the process is focused on the real root causes, not blame. Review should involve those affected, and those with the responsibility. \n\n##### *I (Improve)*\n\nAfter review, it's time to prepare a defined and clear iteration. What actions can be taken to potentially address the areas of opportunity? These actions should be clear, defined, measurable, and focused enough that success or failure can be clearly identified and attributed. In larger organizations, consider A/B testing the action plan between a few teams, and be sure to test and assess action plans at smaller scale before implementing enterprise-wide. \n \nAs you implement action plans for improvement, refresh your MARI cycle to coherently drive progress and evolution.\n\n", - "mode": "markdown" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 0, - "y": 9 - }, - "id": 13, - "interval": null, - "links": [], - "options": { - "content": " \n
\n \"jenkins\"\n

Jenkins

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "description": "", - "gridPos": { - "h": 9, - "w": 7, - "x": 7, - "y": 9 - }, - "id": 12, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"requirement\"\n

New Dashboard

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "transparent": true, - "type": "text" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Data Source Specific Dashboards-Homepage", - "uid": "Lv1XbLHnk", - "version": 2 -} diff --git a/grafana/_archive/DeliveryCapability(RequireJenkinsData).json b/grafana/_archive/DeliveryCapability(RequireJenkinsData).json deleted file mode 100644 index 36aa2d01e9f..00000000000 --- a/grafana/_archive/DeliveryCapability(RequireJenkinsData).json +++ /dev/null @@ -1,470 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 9, - "iteration": 1637051222519, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": true, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/RXJZNpMnz/user-value-specific-dashboards-homepage?orgId=1" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "user_value" - ], - "targetBlank": false, - "title": "metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "datasource": "mysql", - "description": "1. Number of builds executed.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 0 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n count(*)\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Count", - "type": "stat" - }, - { - "cacheTimeout": null, - "datasource": "mysql", - "description": "The percentage of successful, failed, and aborted builds.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 0 - }, - "id": 37, - "interval": null, - "links": [], - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n result as metric,\n count(*) as value\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nGROUP BY 1,2\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Result Distribution", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "Number of successful builds / Number of total builds", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 6 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n 1.0 * count(case when result = 'SUCCESS' then 1 else null end)/count(*)\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Success Rate", - "type": "stat" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. Build success rate over time.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "unit": "percentunit" - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 6 - }, - "hiddenSeries": false, - "id": 50, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _build_success_rate as(\r\n SELECT\r\n DATE_ADD(date(start_time), INTERVAL -$interval(date(start_time))+1 DAY) as time,\r\n result\r\n FROM\r\n jenkins_builds\r\n WHERE\r\n $__timeFilter(start_time)\r\n)\r\n\r\nSELECT \r\n timestamp(time) as time,\r\n 1.0 * sum(case when result = 'SUCCESS' then 1 else 0 end)/ count(*) as \"Build Success Rate\"\r\nFROM _build_success_rate\r\ngroup by 1\r\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Build Success Rate over Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:262", - "decimals": null, - "format": "percentunit", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:263", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "capability", - "user_value" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": true, - "text": "Month", - "value": "DAY" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Time Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "Week", - "value": "DAYOFWEEK" - }, - { - "selected": true, - "text": "Month", - "value": "DAY" - } - ], - "query": "Week : DAYOFWEEK, Month : DAY", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Delivery Capability (require Jenkins data)", - "uid": "3Lv1ImSnk", - "version": 5 -} diff --git a/grafana/_archive/DeliveryCost(RequireGitlabData).json b/grafana/_archive/DeliveryCost(RequireGitlabData).json deleted file mode 100644 index b5faba8349e..00000000000 --- a/grafana/_archive/DeliveryCost(RequireGitlabData).json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 1, - "iteration": 1637051230516, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": true, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/RXJZNpMnz/user-value-specific-dashboards-homepage?orgId=1" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "user_value" - ], - "targetBlank": false, - "title": "metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "datasource": "mysql", - "description": "1. Number of people who have created or reviewed a Pull/Merge Request.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 9, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with all_developers as(\n select \n gitlab_reviewers.name as user_name\n from gitlab_merge_requests\n join gitlab_reviewers on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\n\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_merge_requests.project_id = $repo_id\n union\n select \n distinct author_username as user_name \n from gitlab_merge_requests\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_merge_requests.project_id = $repo_id\n union\n select \n distinct author_name as user_name \n from gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and gitlab_commits.project_id = $repo_id\n)\n\n\nSELECT\n now() AS \"time\",\n count(distinct user_name) as developer_count\nFROM all_developers", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Total Developer Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Total number of Commit Authors.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 6 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n count(distinct author_name) as value\nFROM gitlab_commits\nWHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Author Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of Pull/Merge Request Reviewers.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 6 - }, - "id": 8, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \n now() as time,\n count(distinct name)\nfrom\n gitlab_reviewers", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Reviewer Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Pull Request Reviewer Count/Total Developer Count", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 0.2 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 6 - }, - "id": 16, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with all_user_names as(\n select \n distinct gitlab_reviewers.name as user_name\n from gitlab_reviewers\n join gitlab_merge_requests on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_reviewers.project_id = $repo_id\n union\n select \n distinct author_username as user_name \n from gitlab_merge_requests\n WHERE\n $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n union\n select \n distinct author_name as user_name \n from gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\n),\n\nreviewer as (\n select distinct gitlab_reviewers.name from gitlab_reviewers\n join gitlab_merge_requests on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\nWHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_reviewers.project_id = $repo_id\n)\n\nSELECT\n now() AS \"time\",\n 1.0*count(distinct reviewer.name)/count(distinct user_name) as value\nFROM reviewer, all_user_names", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Reviewer Rate (%)", - "type": "stat" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "cost", - "user_value" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "mysql", - "definition": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Choose Repo", - "multi": false, - "name": "repo_id", - "options": [], - "query": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "refresh": 1, - "regex": "/^(?[^:]+): (?\\d+)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Delivery Cost (require Gitlab data)", - "uid": "TV-BnpM7k", - "version": 7 -} diff --git a/grafana/_archive/DeliveryQuality(RequireJiraAndGitlabData).json b/grafana/_archive/DeliveryQuality(RequireJiraAndGitlabData).json deleted file mode 100644 index 4f384919be4..00000000000 --- a/grafana/_archive/DeliveryQuality(RequireJiraAndGitlabData).json +++ /dev/null @@ -1,1699 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 2, - "iteration": 1637051238473, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": false, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/RXJZNpMnz/user-value-specific-dashboards-homepage?orgId=1" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "user_value" - ], - "targetBlank": false, - "title": "Metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 46, - "interval": null, - "links": [], - "options": { - "content": "
\n
\n \"R&D\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Charts are organized by the Research & Development stages below", - "transparent": true, - "type": "text" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 6 - }, - "id": 48, - "options": { - "content": "
\n
\n \"No.1\"\n

Development

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of Pull/Merge request created.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 10 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM \n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Total number of Pull/Merge request merged.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 10 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n count(*) as value\nFROM\n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n jbgp.jira_board_id = $board_id\n and state = 'merged'\n and $__timeFilter(gitlab_created_at)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Pass Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Pull Request Pass Count/Pull Request Count", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 0.8 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 10 - }, - "id": 37, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n 1.0 * count(case when state = 'merged' then 1 else null end)/count(*)\nFROM\n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Pass Rate (%)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Pull Request Pass Rate over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", value \"pull_request_pass_rate\" of \"2021-06-01\" calculates the PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 16 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _mr_request_rate as(\n SELECT\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n state\n FROM\n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\n WHERE\n jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\n)\n\nSELECT \n timestamp(time) as time,\n 1.0 * count(case when state = 'merged' then 1 else null end)/count(*) as \"Pull Request Pass Rate\"\nFROM _mr_request_rate\nGROUP BY 1\nORDER BY 1\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Pass Rate over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The average round of PR/MR review.", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 22 - }, - "id": 53, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n avg(review_rounds) as value\nFROM \n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n state = 'merged'\n and review_rounds > 0\n and jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Pull Request Review Round", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Avg PR/MR Review Round over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", value \"Avg Pull Request Review Round\" of \"2021-06-01\" calculates the PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 22 - }, - "id": 55, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n timestamp(DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY)) as time,\n avg(review_rounds) as \"Pull Request Review Round\"\nFROM\n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n state = 'merged'\n and review_rounds > 0\n and jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\nGROUP BY 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Pull Request Review Round over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The average round of PR/MR review of PR authors.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 28 - }, - "id": 54, - "options": { - "barWidth": 0.52, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n author_username as Author,\n avg(review_rounds) as value\nFROM \n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\nWHERE\n state = 'merged'\n and review_rounds > 0\n and jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\nGROUP BY 1,2\nORDER BY 3 desc\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Pull Request Review Round by Author", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "One-time pass rate for PR over time.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "One-time Pass Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 56, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _review_rounds as(\n SELECT\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n review_rounds\n FROM\n gitlab_merge_requests gmr\n LEFT JOIN jira_board_gitlab_projects jbgp ON jbgp.gitlab_project_id = gmr.project_id\n WHERE\n state = 'merged'\n and review_rounds > 0\n and jbgp.jira_board_id = $board_id\n and $__timeFilter(gitlab_created_at)\n)\n\nSELECT \n timestamp(time) as time,\n count(case when review_rounds = 1 then true else null end)/count(*) as one_time_pass_rate\nFROM \n _review_rounds\nGROUP BY 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "One-time Pass Rate for Code Reviews over Time", - "type": "timeseries" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 40 - }, - "id": 50, - "options": { - "content": "
\n
\n \"No.2\"\n

Test

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of bugs created.\n2. The bugs being calculated are filtered by \"bug creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 44 - }, - "id": 16, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n count(*) as value\nFROM\n jira_issues ji\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\nWHERE\n std_type = 'Bug'\n and jbi.board_id = $board_id\n and $__timeFilter(created)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Number of Bugs found in Testing", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of bugs created over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", bug_count of \"2021-06-01\" calculates the bugs whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 44 - }, - "id": 18, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _bugs as (\n SELECT\n DATE_ADD(date(ji.created), INTERVAL -$interval(date(ji.created))+1 DAY) as time,\n count(distinct ji.issue_id) as bug_count\n FROM\n jira_issues ji\n LEFT JOIN jira_board_issues jbi ON jbi.issue_id = ji.issue_id\n WHERE\n std_type = 'Bug'\n and jbi.board_id = $board_id\n and $__timeFilter(ji.created)\n group by 1\n order by 1\n) \n\nselect \n timestamp(time) as time,\n bug_count\nfrom _bugs", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Number of Bugs found in Testing over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "1. Total number of bugs found per 1,000 lines of code, including both added and deleted llines of code.\n2. The bugs being calculated are filtered by \"bug creation time\". The lines of code being calculated are filtered by \"commit creation time\" (both are affected by the time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 0.32 - }, - { - "color": "red", - "value": 2.39 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 50 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with bc as (\n\tselect \n\t count(*) as bug_count\n\tfrom \n\t jira_issues ji \n\t left join jira_board_issues jbi on (jbi.issue_id = ji.issue_id)\n\twhere \n\t jbi.board_id = $board_id\n\t and ji.std_type = 'Bug'\n\t and $__timeFilter(ji.created)\n),\n\nloc as (\n\tselect \n\t sum(additions + deletions) as line_count\n\tfrom \n\t gitlab_commits gc \n\t left join jira_board_gitlab_projects jbgp on jbgp.gitlab_project_id = gc.project_id\n\twhere\n\t jbgp.jira_board_id = $board_id\n\t and gc.title not like 'Merge branch %'\n\t and $__timeFilter(authored_date)\n)\n\nselect \n now() as time, \n 1.0 * bc.bug_count / loc.line_count * 1000 as value\nfrom loc,bc\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Bug Count per 1k lines of code (Total)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Bug Count per 1k lines of code over time.\n2. When Time Interval is set to \"month\", bug_count_per_1k_LOC of \"2021-06-01\" calculates the bugs whose creation time falls under [2020-06-01, 2020-07-01) and lines of code whose commit creation time falls under [2020-06-01, 2020-07-01) ", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 50 - }, - "id": 22, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with bc as (\n\tselect\n\t DATE_ADD(date(ji.created), INTERVAL -$interval(date(ji.created))+1 DAY) as time,\n\t count(*) as bug_count\n\tfrom \n\t jira_issues ji \n\t left join jira_board_issues jbi on jbi.issue_id = ji.issue_id\n\twhere \n\t jbi.board_id = $board_id\n\t and ji.type = 'Bug'\n\t and $__timeFilter(ji.created)\n\tgroup by 1\n),\nloc as (\n\tselect \n\t DATE_ADD(date(gc.authored_date), INTERVAL -$interval(date(gc.authored_date))+1 DAY) as time,\n\t sum(additions + deletions) as line_count\n\tfrom \n\t gitlab_commits gc \n\t left join jira_board_gitlab_projects jbgp on jbgp.gitlab_project_id = gc.project_id\n\twhere \n\t jbgp.jira_board_id = $board_id\n\t and gc.title not like 'Merge branch %'\n\t and $__timeFilter(authored_date)\n\tgroup by 1\n),\nbug_count_per_1k_loc as(\n select \n bc.time,\n 1.0 * bc.bug_count / loc.line_count * 1000 as bug_count_per_1k_loc\n from \n loc\n left join bc on bc.time = loc.time\n where\n bc.bug_count is not null \n and loc.line_count is not null\n)\n\nselect \n timestamp(time) as time, \n bug_count_per_1k_loc \nfrom bug_count_per_1k_loc\norder by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Bug Count per 1k lines of code (Breakdown by Time)", - "type": "timeseries" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 56 - }, - "id": 52, - "options": { - "content": "
\n
\n \"No.3\"\n

Operation

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of incidents created.\n2. The incidents being calculated are filtered by \"incident creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 60 - }, - "id": 38, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n count(*) as value\nFROM\n jira_issues ji\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\nWHERE\n std_type = 'Incident'\n and jbi.board_id = $board_id\n and $__timeFilter(created)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Number of Incidents found after shipping", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of incidents created over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", incident_count of \"2021-06-01\" calculates the incidents whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Incident Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 60 - }, - "id": 39, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _incidents as (\n SELECT\n DATE_ADD(date(ji.created), INTERVAL -$interval(date(ji.created))+1 DAY) as time,\n count(distinct ji.issue_id) as incident_count\n FROM\n jira_issues ji\n LEFT JOIN jira_board_issues jbi ON jbi.issue_id = ji.issue_id\n WHERE\n std_type = 'Incident'\n and jbi.board_id = $board_id\n and $__timeFilter(ji.created)\n group by 1\n order by 1\n) \n\nselect \n timestamp(time) as time,\n incident_count\nfrom _incidents", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Number of Incidents found after shipping over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "1. Total number of incidents found per 1,000 lines of code, including both added and deleted llines of code.\n2. The incidents being calculated are filtered by \"incident creation time\". The lines of code being calculated are filtered by \"commit creation time\" (both are affected by the time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 0.32 - }, - { - "color": "red", - "value": 2.39 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 66 - }, - "id": 28, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with ic as (\n\tselect \n\t count(*) as incident_count\n\tfrom \n\t jira_issues ji \n\t left join jira_board_issues jbi on (jbi.issue_id = ji.issue_id)\n\twhere \n\t jbi.board_id = $board_id\n\t and ji.type = 'Incident'\n\t and $__timeFilter(ji.created)\n),\n\nloc as (\n\tselect \n\t sum(additions + deletions) as line_count\n\tfrom \n\t gitlab_commits gc \n\t left join jira_board_gitlab_projects jbgp on jbgp.gitlab_project_id = gc.project_id\n\twhere\n\t jbgp.jira_board_id = $board_id\n\t and gc.title not like 'Merge branch %'\n\t and $__timeFilter(authored_date)\n)\n\nselect \n now() as time, \n 1.0 * ic.incident_count / loc.line_count * 1000 as value\nfrom loc,ic", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Incident Count per 1k lines of code (Total)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Incident Count per 1k lines of code over time.\n2. When Time Interval is set to \"month\", incident_count_per_1k_LOC of \"2021-06-01\" calculates the incidents whose creation time falls under [2020-06-01, 2020-07-01) and lines of code whose commit creation time falls under [2020-06-01, 2020-07-01) ", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Incident Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 66 - }, - "id": 30, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with ic as (\n\tselect \n\t DATE_ADD(date(ji.created), INTERVAL -$interval(date(ji.created))+1 DAY) as time,\n\t count(*) as incident_count\n\tfrom \n\t jira_issues ji \n\t left join jira_board_issues jbi on jbi.issue_id = ji.issue_id\n\twhere \n\t jbi.board_id = $board_id\n\t and ji.type = 'Incident'\n\t and $__timeFilter(ji.created)\n\tgroup by 1\n),\nloc as (\n\tselect \n\t DATE_ADD(date(gc.authored_date), INTERVAL -$interval(date(gc.authored_date))+1 DAY) as time,\n\t sum(additions + deletions) as line_count\n\tfrom \n\t gitlab_commits gc \n\t left join jira_board_gitlab_projects jbgp on jbgp.gitlab_project_id = gc.project_id\n\twhere \n\t jbgp.jira_board_id = $board_id\n\t and gc.title not like 'Merge branch %'\n\t and $__timeFilter(authored_date)\n\tgroup by 1\n),\nincident_count_per_1k_loc as(\n select \n ic.time,\n 1.0 * ic.incident_count / loc.line_count * 1000 as incident_count_per_1k_loc\n from \n loc\n left join ic on ic.time = loc.time\n where\n ic.incident_count is not null \n and loc.line_count is not null\n)\n\nselect \n timestamp(time) as time, \n incident_count_per_1k_loc \nfrom incident_count_per_1k_loc\norder by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Incident Count per 1k lines of code (Breakdown by Time)", - "type": "timeseries" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "quality", - "user_value" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": false, - "text": "Month", - "value": "DAY" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Time Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "Week", - "value": "DAYOFWEEK" - }, - { - "selected": true, - "text": "Month", - "value": "DAY" - } - ], - "query": "Week : DAYOFWEEK, Month : DAY", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "allValue": null, - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "mysql", - "definition": "select distinct concat(name, ': ', board_id) from jira_boards", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Choose Board", - "multi": false, - "name": "board_id", - "options": [], - "query": "select distinct concat(name, ': ', board_id) from jira_boards", - "refresh": 1, - "regex": "/^(?[^:]+): (?\\d+)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Delivery Quality (require Jira and Gitlab data)", - "uid": "nUC-7tGnk", - "version": 14 -} diff --git a/grafana/_archive/DeliveryVelocity(RequireJiraAndGitlabData).json b/grafana/_archive/DeliveryVelocity(RequireJiraAndGitlabData).json deleted file mode 100644 index 8df95d940af..00000000000 --- a/grafana/_archive/DeliveryVelocity(RequireJiraAndGitlabData).json +++ /dev/null @@ -1,4093 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 3, - "iteration": 1637051247070, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": true, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/RXJZNpMnz/user-value-specific-dashboards-homepage?orgId=1" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "user_value" - ], - "targetBlank": false, - "title": "Metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 5, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 89, - "interval": null, - "links": [], - "options": { - "content": "
\n
\n \"R&D\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Charts are organized by the Research & Development stages below", - "transparent": true, - "type": "text" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 76, - "options": { - "content": "
\n
\n \"Cross-stage\"\n

Cross-stage Metrics

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. The average lead time of requirements.\n2. Lead time refers to the length of time from requirement creation to delivery.\n3. The requirements being calculated are filtered by \"requirement resolution time\" (time filter at the upper-right corner) and \"Jira board\"(\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 14 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 9 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "/^value$/", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \r\n now() as time,\r\n avg(lead_time/1440) as value\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Requirement'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\ngroup by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Avg Requirement Lead Time (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The lead time at which 80% requirements‘ lead time lies below it.\n2. The requirements being calculated are filtered by \"requirement resolution time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 21 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 9 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n ji.lead_time,\r\n percent_rank() over (order by lead_time asc) as ranks\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect\r\n now() as time,\r\n max(lead_time/1440) as value\r\nfrom _ranks\r\nwhere \r\n ranks <= 0.8\r\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "80th Percentile Requirement Lead Time (day)", - "type": "stat" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. The average requirement lead time over time.\n2. The time granularity can be switched to Week or Month by \"Time Interval\" above. \n3. When Time Interval is set to \"Month\", the average_lead_time of \"2021-06-01\" refers to the average lead time of requirements whose resolution time falls under [2020-06-01, 2020-07-01).", - "fieldConfig": { - "defaults": { - "unit": "short" - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 9 - }, - "hiddenSeries": false, - "id": 17, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select \r\n DATE_ADD(date(resolution_date), INTERVAL -$interval(date(resolution_date))+1 DAY) as time,\r\n lead_time\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n timestamp(time) as time,\r\n avg(lead_time/1440) as average_lead_time\r\nfrom _requirement\r\ngroup by 1\r\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requirement Lead Time over Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:242", - "format": "short", - "label": "Lead Time(day)", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:243", - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. The cumulative distribution of requirement lead time. \n2. Each point refers to the percent rank of a lead time.", - "fill": 0, - "fillGradient": 4, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 15 - }, - "hiddenSeries": false, - "id": 15, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 8, - "nullPointMode": "null", - "options": { - "alertThreshold": false - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 0.5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n round(ji.lead_time/1440) as lead_time_day\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n order by lead_time_day asc\r\n)\r\n\r\nselect \r\n now() as time,\r\n lpad(concat(lead_time_day,'d'), 4, ' ') as metric,\r\n percent_rank() over (order by lead_time_day asc) as value\r\nfrom _ranks\r\norder by lead_time_day asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "$$hashKey": "object:469", - "colorMode": "ok", - "fill": true, - "line": true, - "op": "lt", - "value": 0.8, - "yaxis": "right" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requirement Lead Time (Cumulative Distribution)", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "current" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:76", - "format": "percentunit", - "label": "Percent Rank (%)", - "logBase": 1, - "max": "1.2", - "min": null, - "show": true - }, - { - "$$hashKey": "object:77", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "mysql", - "description": "The average lead time of requirements under different Jira Epics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Lead Time (day)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 21 - }, - "id": 18, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n ji.issue_id,\r\n epic_key,\r\n ji.lead_time\r\n from\r\n jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n ji.std_type = 'Requirement'\r\n and ji.std_status = 'Resolved'\r\n and ji.epic_key is not null\r\n and ji.epic_key != ''\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n now() as time,\r\n epic_key as \"Epic Key\",\r\n avg(lead_time/1440) as value\r\nfrom _requirement\r\ngroup by 1,2\r\norder by 3 desc\r\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Requirement Lead Time by Epic", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "The average time spent in each state of a requirement, including all history states that have been used.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Lead Time(day)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 74, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 27 - }, - "id": 97, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with operation_history as(\n\tSELECT\n\t\tjc.issue_id,\n\t\tjc.created as operation_time,\n\t\tjci.changelog_id,\n\t\tjci.from,\n\t\tjci.from_string,\n\t\tjci.to,\n\t\tjci.to_string,\n\t\tji.created as issue_creation_time,\n\t\tcase \n\t\t\twhen lag(jc.created) over(PARTITION BY jc.issue_id ORDER BY jc.created ASC) is not null then\n\t\t\tTIMESTAMPDIFF(MINUTE, lag(jc.created) over(PARTITION BY jc.issue_id ORDER BY jc.created ASC), jc.created)\n\t\t\telse TIMESTAMPDIFF(MINUTE, ji.created, jc.created) end as time_in_status\n\tFROM\n\t\tjira_changelogs jc\n\t\tJOIN jira_changelog_items jci ON jc.changelog_id = jci.changelog_id\n\t\tJOIN jira_issues ji ON jc.issue_id = ji.issue_id\n\t\tjoin jira_board_issues jbi on ji.issue_id = jbi.issue_id\n\tWHERE\n\t\tjci.field = 'status'\n\t\tand ji.std_type = 'Requirement'\n\t\tand ji.std_status = 'Resolved'\n\t\tand $__timeFilter(ji.resolution_date)\n and jbi.board_id = $board_id\n)\n\nSELECT\n now() as time,\n\tfrom_string as \"Requirement Status\",\n\tavg(time_in_status)/1440 as \"Time in Status\"\nFROM operation_history\nGROUP BY 1,2\nORDER BY 3 desc", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Requirement Lead Time by Status", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "1. Compare the average time spent in each state of a requirement in the last 2 months.\n2. The requirements being calculated are the requirements delivered in the last 2 months.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Lead Time(day)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 74, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 33 - }, - "id": 98, - "options": { - "barWidth": 0.9, - "groupWidth": 0.6, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with status_change_history as(\n\tSELECT\n\t\tjc.issue_id,\n\t\tjc.created as operation_time,\n\t\tjci.changelog_id,\n\t\tjci.from,\n\t\tjci.from_string,\n\t\tjci.to,\n\t\tjci.to_string,\n\t\tji.created as issue_creation_time,\n\t\tlag(jc.created) over(PARTITION BY jc.issue_id ORDER BY jc.created ASC) as last_operation_time,\n\t\tcase \n\t\t\twhen lag(jc.created) over(PARTITION BY jc.issue_id ORDER BY jc.created ASC) is not null then\n\t\t\tTIMESTAMPDIFF(MINUTE, lag(jc.created) over(PARTITION BY jc.issue_id ORDER BY jc.created ASC), jc.created)\n\t\t\telse TIMESTAMPDIFF(MINUTE, ji.created, jc.created) end as time_in_status\n\tFROM\n\t\tjira_changelogs jc\n\t\tJOIN jira_changelog_items jci ON jc.changelog_id = jci.changelog_id\n\t\tJOIN jira_issues ji ON jc.issue_id = ji.issue_id\n\t\tjoin jira_board_issues jbi on ji.issue_id = jbi.issue_id\n\tWHERE\n\t\tjci.field = 'status'\n\t\tand ji.std_type = 'Requirement'\n\t\tand ji.std_status = 'Resolved'\n\t\tand $__timeFilter(ji.resolution_date)\n and jbi.board_id = $board_id\n),\n\ntime_in_status as(\n\tSELECT \n\t\tDATE_ADD(date(issue_creation_time), INTERVAL -DAY(date(issue_creation_time))+1 DAY) as time,\n\t\tfrom_string,\n\t\tavg(time_in_status)/60 as time_in_status\n\tFROM status_change_history\n\tGROUP BY 1,2\n),\n\nlast_month as(\n\tselect \n\t\tDATE_ADD(date(CURDATE()), INTERVAL -DAY(date(CURDATE()))+1 DAY) as last_month\n),\n\nmonth_before_last_month as(\n\tSELECT \n\t\tDATE_ADD(DATE_ADD(date(CURDATE()), INTERVAL -DAY(date(CURDATE()))+1 DAY), INTERVAL -1 MONTH) as two_month_before\n),\n\n\nlast_month_record as(\n\tSELECT\n\t\tfrom_string as issue_status,\n\t\tcase when time in (SELECT last_month from last_month) then time_in_status else null end as last_month\n\tfrom time_in_status\n\tWHERE \n\t\ttime in (SELECT last_month from last_month)\n),\n\ntwo_month_before_record as(\n\tSELECT\n\t\tfrom_string as issue_status,\n\t\tcase when time in (SELECT two_month_before from month_before_last_month) then time_in_status else null end as two_month_before\n\tfrom time_in_status\n\tWHERE \n\t\ttime in (SELECT two_month_before from month_before_last_month)\n)\n\nSELECT\n\tnow() as time,\n\tmbr.two_month_before AS \"The Month Before Last\",\n\tlmr.last_month AS \"Last Month\",\n\tlmr.issue_status\nFROM last_month_record lmr\nleft join two_month_before_record mbr on lmr.issue_status = mbr.issue_status ", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Requirement Lead Time by Status in Last 2 Months", - "type": "barchart" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 39 - }, - "id": 92, - "options": { - "content": "
\n
\n \"No.1\"\n

Requirement

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of requirements created.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 43 - }, - "id": 2, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "select \r\n now() as time,\r\n count(*) as value\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Requirement'\r\n and created is not null \r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\ngroup by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Requirement Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The number of requirements created over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", the requirement_count of \"2021-06-01\" refers to the number of requirements whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 43 - }, - "id": 6, - "interval": null, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n ji.issue_id AS id,\r\n DATE_ADD(date(created), INTERVAL -$interval(date(created))+1 DAY) as time\r\n from\r\n jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and created is not null \r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect\r\n timestamp(time) as time,\r\n count(distinct id) as requirement_count\r\nfrom _requirement\r\ngroup by 1\r\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Requirement Count over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "1. The number of requirements under different Jira Epics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "No. of Requirement", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 49 - }, - "id": 7, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n ji.issue_id,\r\n ji.epic_key\r\n from\r\n jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and created is not null\r\n and epic_key is not null\r\n and epic_key != ''\r\n and std_status = 'Resolved'\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n now() as time,\r\n epic_key as \"Epic Key\",\r\n count(*) as value\r\nfrom _requirement\r\ngroup by 1,2\r\norder by 3 desc\r\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Requirement Count by Epic", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "1. Number of requirements that have been delivered.\n2. A requirement is delivered when its status equals 'delivered'.\n3. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 55 - }, - "id": 25, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "select\r\n now() as time,\r\n count(*) as delivered_count\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Requirement'\r\n and ji.std_status = 'Resolved'\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Delivered Requirement Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Delivered Requirement Count/Requirement Count (Total)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 50 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 55 - }, - "id": 22, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n count(distinct ji.issue_id) as total_count,\r\n count(distinct case when ji.std_status = 'Resolved' then ji.issue_id else null end) as delivered_count\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\n\r\nselect \r\n now() as time,\r\n 1.0 * delivered_count/total_count as requirement_delivery_rate\r\nfrom _requirement", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Requirement Delivery Rate", - "type": "stat" - }, - { - "cacheTimeout": null, - "datasource": "mysql", - "description": "1. The requirement delivery rate over time.\n2. When Time Interval is set to \"month\", value \"requirement_delivery_rate\" of \"2021-06-01\" calculates the requirements whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Delivery Rate (%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 55 - }, - "id": 23, - "links": [], - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n DATE_ADD(date(created), INTERVAL -$interval(date(created))+1 DAY) as time,\r\n count(distinct ji.issue_id) as total_count,\r\n count(distinct case when ji.std_status = 'Resolved' then ji.issue_id else null end) as delivered_count\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n group by 1\r\n)\r\n\r\n\r\nselect \r\n timestamp(time) as time,\r\n 1.0 * delivered_count/total_count as requirement_delivery_rate\r\nfrom _requirement\r\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Requirement Delivery Rate over Time", - "type": "timeseries" - }, - { - "cacheTimeout": null, - "datasource": "mysql", - "description": "The average delivery rate of requirements under different Jira Epics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Delivery Rate(%)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 61 - }, - "id": 24, - "links": [], - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select\r\n ji.epic_key as metric,\r\n count(distinct ji.issue_id) as total_count,\r\n count(distinct case when ji.std_status = 'Resolved' then ji.issue_id else null end) as delivered_count\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and epic_key is not null\r\n and epic_key != ''\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n group by 1\r\n)\r\n\r\n\r\nselect \r\n now() as time,\r\n metric,\r\n 1.0 * delivered_count/total_count as requirement_delivery_rate\r\nfrom _requirement\r\norder by 3 desc\r\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Requirement Delivery Rate by Epic", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "1. The average story point (workload) of requirements.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 67 - }, - "id": 93, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "select \r\n now() as time,\r\n avg(std_story_point) as value\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Requirement'\r\n and std_story_point != 0\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\ngroup by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Avg Requirement Granunarity(Story Point)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The story point/estimated hours at which 80% requirements' story point/estimated hours lies below it.\n2. The requirements being calculated are filtered by \"requirement creation time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 67 - }, - "id": 94, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n std_story_point,\r\n percent_rank() over (order by std_story_point asc) as ranks\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_story_point != 0\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect\r\n now() as time,\r\n max(std_story_point) as value\r\nfrom _ranks\r\nwhere \r\n ranks <= 0.8\r\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "80th Percentile Requirement Granunarity(Story point)", - "type": "stat" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "The average and 80th percentile requirement granularity over time.", - "fieldConfig": { - "defaults": { - "unit": "short" - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 67 - }, - "hiddenSeries": false, - "id": 96, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _requirement as(\r\n select \r\n ji.issue_id,\r\n DATE_ADD(date(resolution_date), INTERVAL -$interval(date(resolution_date))+1 DAY) as time,\r\n std_story_point\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_story_point != 0\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n timestamp(time) as time,\r\n avg(std_story_point) as average_story_point\r\nfrom _requirement\r\ngroup by 1\r\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requirement Granularity over Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:242", - "format": "short", - "label": "Story Points", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:243", - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. The cumulative distribution of requirement granularity.\n2. Each point refers to the percent rank of a value of granularity.", - "fill": 0, - "fillGradient": 4, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 73 - }, - "hiddenSeries": false, - "id": 95, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 8, - "nullPointMode": "null", - "options": { - "alertThreshold": false - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 5, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n round(std_story_point) as std_story_point\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Requirement'\r\n and std_story_point != 0\r\n and $__timeFilter(created)\r\n and jbi.board_id = $board_id\r\n order by std_story_point asc\r\n)\r\n\r\nselect \r\n now() as time,\r\n lpad(std_story_point, 4, ' ') as metric,\r\n percent_rank() over (order by std_story_point asc) as value\r\nfrom _ranks\r\norder by std_story_point asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "$$hashKey": "object:469", - "colorMode": "ok", - "fill": true, - "line": true, - "op": "lt", - "value": 0.8, - "yaxis": "right" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requirement Granularity (Cumulative Distribution)", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "current" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:76", - "format": "percentunit", - "label": "Percent Rank (%)", - "logBase": 1, - "max": "1.2", - "min": null, - "show": true - }, - { - "$$hashKey": "object:77", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 79 - }, - "id": 71, - "options": { - "content": "
\n
\n \"No.2\"\n

Development

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Average PR/MR review time.\n2. PR/MR review time refers to the time between the creation time and merged time of a PR/MR.\n3. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 3 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 83 - }, - "id": 80, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \n now() as time,\n avg(TIMESTAMPDIFF(MINUTE,gitlab_created_at,merged_at))/1440 as value\nfrom gitlab_merge_requests\nWHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Avg Pull Request Review Time (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The 80th percentile PR/MR review time.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 7 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 83 - }, - "id": 83, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\n select \n TIMESTAMPDIFF(SECOND,gitlab_created_at,merged_at)/86400 as metric,\n percent_rank() over (order by TIMESTAMPDIFF(MINUTE,gitlab_created_at,merged_at) asc) as ranks\n from gitlab_merge_requests\n WHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n )\n \nselect\n now() as time,\n max(metric) as value\nfrom _ranks\nwhere \n ranks <= 0.8\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "80th Percentile Pull Request Review Time(day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The average and 80th percentile PR/MR review time over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", the average review time of \"2021-06-01\" refers to the review time of PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Review Time (day)", - "axisPlacement": "auto", - "axisSoftMin": -3, - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 83 - }, - "id": 82, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with reviewed_mr as(\n select\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n TIMESTAMPDIFF(SECOND,first_comment_time,merged_at)/86400 as review_time\n from gitlab_merge_requests\n WHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n)\n\nselect \n timestamp(time) as time,\n avg(review_time) as \"Average Review Time\"\nfrom reviewed_mr\ngroup by 1\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Review Time over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "1. Total number of commits created.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 89 - }, - "id": 34, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM gitlab_commits\nWHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of commits created over time.\n2. When Time Interval is set to \"month\", the commit_count\" of \"2021-06-01\" calculates the commits whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Commit Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 6, - "y": 89 - }, - "id": 36, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _commits as(\n SELECT\n DATE_ADD(date(authored_date), INTERVAL -$interval(date(authored_date))+1 DAY) as time,\n count(*) as commit_count\n FROM gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\n group by 1\n)\n\nSELECT \n timestamp(time) as time,\n commit_count as \"Commit Count\"\nFROM _commits\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Count over Time", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "The number of commits from different repos.", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 6, - "x": 18, - "y": 89 - }, - "hiddenSeries": false, - "id": 37, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n gp.name as metric,\n count(*) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id\nWHERE\n $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1,2\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Commit Count by Repo", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "total" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:89", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:90", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "mysql", - "description": "The number of commits from different commit authors.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 95 - }, - "id": 38, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n gc.author_name as \"Author Name\",\n count(*) as value\nFROM gitlab_commits gc \n join gitlab_projects gp on gc.project_id = gp.gitlab_id\nWHERE\n $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1,2\norder by 3 desc\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Count by Author", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "1. Sum of the number of added lines of code under all commits.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 101 - }, - "id": 42, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n sum(additions) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id \nWHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added LOC", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Sum of the number of deleted lines of code under all commits.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 101 - }, - "id": 43, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n sum(deletions) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id \nWHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Deleted LOC", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Added/deleted number of lines of code over time.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "LOC", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 101 - }, - "id": 47, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with loc as(\n SELECT\n DATE_ADD(date(authored_date), INTERVAL -$interval(date(authored_date))+1 DAY) as time,\n sum(additions) as added_LOC,\n sum(deletions) as deleted_LOC\n FROM gitlab_commits\n WHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and project_id = $repo_id\n group by 1\n)\n\nSELECT \n timestamp(time) as time,\n added_LOC as \"Added LOC\",\n deleted_LOC as \"Deleted LOC\"\nFROM loc\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added and Deleted LOC over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The number of added/deleted LOC from different commit authors.", - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "orange", - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "LOC", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 107 - }, - "id": 49, - "options": { - "barWidth": 0.9, - "groupWidth": 0.6, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n sum(additions) as \"Added LOC\",\n sum(deletions) as \"Removed LOC\",\n gc.author_name\nFROM gitlab_commits gc \n join gitlab_projects gp on gc.project_id = gp.gitlab_id\nWHERE\n author_name is not null\n and message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1,4\norder by 2 desc\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added and Deleted LOC by Author", - "type": "barchart" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 113 - }, - "id": 77, - "options": { - "content": "
\n
\n \"No.3\"\n

Test

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. The average bug age.\n2. Bug age refers to the length of time from bug creation to resolution.\n3. The bugs being calculated are filtered by \"bug resolution time\" (time filter at the upper-right corner) and \"Jira board\"(\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 7 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 117 - }, - "id": 53, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "/^value$/", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \r\n now() as time,\r\n avg(lead_time/1440) as value\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Bug'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\ngroup by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Bug Age (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The bug age at which 80% bugs‘ age lies below it.\n2. The bugs being calculated are filtered by \"bug resolution time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 14 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 117 - }, - "id": 65, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n ji.lead_time,\r\n percent_rank() over (order by lead_time asc) as ranks\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Bug'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect\r\n now() as time,\r\n max(lead_time/1440) as value\r\nfrom _ranks\r\nwhere \r\n ranks <= 0.8\r\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "80th Percentile Bug Age (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The average and 80th percentile bug age over time.\n2. The time granularity can be switched to Week or Month by \"Time Interval\" above. \n3. When Time Interval is set to \"Month\", the average_bug_age of \"2021-06-01\" refers to the average age of bugs whose resolution time falls under [2020-06-01, 2020-07-01).", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Age (day)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 117 - }, - "id": 66, - "interval": "", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _bugs as(\r\n select \r\n ji.issue_id,\r\n DATE_ADD(date(resolution_date), INTERVAL -$interval(date(resolution_date))+1 DAY) as time,\r\n lead_time,\r\n round(lead_time/1440) as lead_time_day\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Bug'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n timestamp(time) as time,\r\n avg(lead_time/1440) as \"Average Lead Time\"\r\nfrom _bugs\r\ngroup by 1\r\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Bug Age over Time", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. The cumulative distribution of bug age. \n2. Each point refers to the percent rank of a bug age.", - "fieldConfig": { - "defaults": { - "unit": "percentunit" - }, - "overrides": [] - }, - "fill": 0, - "fillGradient": 4, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 123 - }, - "hiddenSeries": false, - "id": 61, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 8, - "nullPointMode": "null", - "options": { - "alertThreshold": false - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 0.5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n round(ji.lead_time/1440) as lead_time_day\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Bug'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n order by lead_time_day asc\r\n)\r\n\r\nselect \r\n now() as time,\r\n lpad(concat(lead_time_day,'d'), 4, ' ') as metric,\r\n percent_rank() over (order by lead_time_day asc) as value\r\nfrom _ranks\r\norder by lead_time_day asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "$$hashKey": "object:912", - "colorMode": "ok", - "fill": true, - "line": true, - "op": "lt", - "value": 0.8, - "yaxis": "right" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Bug Age (Cumulative Distribution)", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "total" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:76", - "format": "percentunit", - "label": "Percent Rank (%)", - "logBase": 1, - "max": "1.2", - "min": null, - "show": true - }, - { - "$$hashKey": "object:77", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "mysql", - "description": "The average time spent on fixing bugs under different Jira Epics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Age(day)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 129 - }, - "id": 59, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _bugs as(\r\n select\r\n ji.issue_id,\r\n ji.epic_key,\r\n ji.lead_time\r\n from\r\n jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Bug'\r\n and std_status = 'Resolved'\r\n and epic_key is not null\r\n and epic_key != ''\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n now() as time,\r\n epic_key as \"Epic Key\",\r\n avg(lead_time)/1440 as \"Bug Age\"\r\nfrom _bugs\r\ngroup by 1,2\r\norder by 3 desc\r\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Bug Age by Epic", - "type": "barchart" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 135 - }, - "id": 78, - "options": { - "content": "
\n
\n \"No.4\"\n

Operation

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. The average incident age.\n2. Incident age refers to the length of time from incident creation to resolution.\n3. The incidents being calculated are filtered by \"incident resolution time\" (time filter at the upper-right corner) and \"Jira board\"(\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 3 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 139 - }, - "id": 64, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "/^value$/", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \r\n now() as time,\r\n avg(lead_time/1440) as value\r\nfrom jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\nwhere \r\n std_type = 'Incident'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\ngroup by 1;", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Incident Age (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The incident age at which 80% incidents‘ age lies below it.\n2. The incidents being calculated are filtered by \"incident resolution time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 7 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 139 - }, - "id": 55, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n ji.lead_time,\r\n percent_rank() over (order by lead_time asc) as ranks\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Incident'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect\r\n now() as time,\r\n max(lead_time/1440) as value\r\nfrom _ranks\r\nwhere \r\n ranks <= 0.8\r\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "80th Percentile Incident Age (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The average and 80th percentile incident age over time.\n2. The time granularity can be switched to Week or Month by \"Time Interval\" above. \n3. When Time Interval is set to \"Month\", the average_incident_age of \"2021-06-01\" refers to the average age of incidents whose resolution time falls under [2020-06-01, 2020-07-01).", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Incident Age (day)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 139 - }, - "id": 57, - "interval": "", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _incidents as(\r\n select \r\n ji.issue_id,\r\n DATE_ADD(date(resolution_date), INTERVAL -$interval(date(resolution_date))+1 DAY) as time,\r\n lead_time,\r\n round(lead_time/1440) as lead_time_day\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Incident'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n timestamp(time) as time,\r\n avg(lead_time/1440) as \"Average Lead Time\"\r\nfrom _incidents\r\ngroup by 1\r\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Incident Age over Time", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. The cumulative distribution of incident age.\n2. Each point refers to the percent rank of a incident age.", - "fieldConfig": { - "defaults": { - "unit": "percentunit" - }, - "overrides": [] - }, - "fill": 0, - "fillGradient": 4, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 145 - }, - "hiddenSeries": false, - "id": 68, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 8, - "nullPointMode": "null", - "options": { - "alertThreshold": false - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 0.5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\r\n select \r\n round(ji.lead_time/1440) as lead_time_day\r\n from jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Incident'\r\n and std_status = 'Resolved'\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n order by lead_time_day asc\r\n)\r\n\r\nselect \r\n now() as time,\r\n lpad(concat(lead_time_day,'d'), 4, ' ') as metric,\r\n percent_rank() over (order by lead_time_day asc) as value\r\nfrom _ranks\r\norder by lead_time_day asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [ - { - "$$hashKey": "object:1070", - "colorMode": "ok", - "fill": true, - "line": true, - "op": "lt", - "value": 0.8, - "yaxis": "right" - } - ], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Incident Age (Cumulative Distribution)", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "total" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:76", - "format": "percentunit", - "label": "Percent Rank (%)", - "logBase": 1, - "max": "1.2", - "min": null, - "show": true - }, - { - "$$hashKey": "object:77", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": false - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "mysql", - "description": "The average time spent on fixing incidents under different Jira Epics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Incident Age(day)", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 151 - }, - "id": 67, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": { - "valueSize": 1 - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _incidents as(\r\n select\r\n ji.issue_id,\r\n ji.epic_key,\r\n ji.lead_time\r\n from\r\n jira_issues ji\r\n join jira_board_issues jbi on ji.issue_id = jbi.issue_id\r\n where \r\n std_type = 'Incident'\r\n and std_status = 'Resolved'\r\n and epic_key is not null\r\n and epic_key != ''\r\n and $__timeFilter(resolution_date)\r\n and jbi.board_id = $board_id\r\n)\r\n\r\nselect \r\n now() as time,\r\n epic_key as \"Epic Key\",\r\n avg(lead_time)/1440 as \"Incident Age\"\r\nfrom _incidents\r\ngroup by 1,2\r\norder by 3 desc\r\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Incident Age by Epic", - "type": "barchart" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "velocity", - "user_value" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": false, - "text": "Month", - "value": "DAY" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Time Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "Week", - "value": "DAYOFWEEK" - }, - { - "selected": true, - "text": "Month", - "value": "DAY" - } - ], - "query": "Week : DAYOFWEEK, Month : DAY", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "allValue": null, - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "mysql", - "definition": "select distinct concat(name, ': ', board_id) from jira_boards", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Choose Board", - "multi": true, - "name": "board_id", - "options": [], - "query": "select distinct concat(name, ': ', board_id) from jira_boards", - "refresh": 1, - "regex": "/^(?[^:]+): (?\\d+)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "allValue": null, - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "mysql", - "definition": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Choose Repo", - "multi": true, - "name": "repo_id", - "options": [], - "query": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "refresh": 1, - "regex": "/^(?[^:]+): (?\\d+)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-5y", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Delivery Velocity (require Jira and Gitlab data)", - "uid": "ZfaFQeM7k", - "version": 20 -} diff --git a/grafana/_archive/EeGitHubReleaseQualityAndContributionAnalysis.json b/grafana/_archive/EeGitHubReleaseQualityAndContributionAnalysis.json deleted file mode 100644 index 02bb00ec3c5..00000000000 --- a/grafana/_archive/EeGitHubReleaseQualityAndContributionAnalysis.json +++ /dev/null @@ -1,2764 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 21, - "iteration": 1668492584684, - "links": [], - "panels": [ - { - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 49, - "title": "Row title", - "type": "row" - }, - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 1 - }, - "id": 45, - "panels": [], - "title": "Quality", - "type": "row" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 7, - "x": 0, - "y": 2 - }, - "id": 15, - "options": { - "displayLabels": [ - "name", - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the bug distribution in major versions\nwith bugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/tags/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.type, i.title, i.description\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\tand i.type = 'BUG'\n)\n\n\nSELECT \n\tconcat(SUBSTRING_INDEX(biet.tag_name,'.',2), '.x') as minor_version,\n\tcount(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nGROUP BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "1. Bug Distribution [Minor Versions]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Count", - "axisPlacement": "left", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 2 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "cost_percentage(bugfixing commits/total commits)" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - { - "id": "custom.axisLabel", - "value": "Cost Percentage(%)" - }, - { - "id": "custom.axisPlacement", - "value": "right" - }, - { - "id": "unit", - "value": "percentunit" - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 17, - "x": 7, - "y": 2 - }, - "id": 29, - "options": { - "barWidth": 0.7, - "groupWidth": 0.3, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": { - "valueSize": 12 - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the number of fixed bugs in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n -- distinct new_ref_id, old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_bugs_of_tags as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'tags/', -1) as tag_name, \n\t\t-- SUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\tcount(*) as bug_count\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n\t-- GROUP BY 1,2\n\t GROUP BY 1\n),\n\n_combine_pr as (\n select pull_request_id as id, commit_sha from pull_request_commits left join pull_requests p on pull_request_commits.pull_request_id = p.id\n where p.base_repo_id in ($repo_id)\n union\n select id, merge_commit_sha as commit_sha from pull_requests where base_repo_id in ($repo_id)\n),\n\n_commit_count_of_pr as(\n select\n SUBSTRING_INDEX(rcd.new_ref_id,'tags/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) as repo_id,\n pr.id as pull_request_id,\n count(c.sha) as commit_count\n FROM \n refs_commits_diffs rcd\n\t\tleft join commits c on rcd.commit_sha = c.sha\n\t\t-- left join pull_request_commits prc on c.sha = prc.commit_sha\n\t\tleft join _combine_pr pr on c.sha = pr.commit_sha\n\twhere\n\t\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT SUBSTRING_INDEX(new_ref_id,':', -1) FROM _last_5_tags)\n\tgroup by 1,2,3\n),\n\n_pr_worktype as(\n select\n distinct pri.pull_request_id,i.type\n from\n pull_request_issues pri\n left join pull_requests pr on pri.pull_request_id = pr.id\n left join issues i on pri.issue_id = i.id\n where \n i.issue_key != 0\n),\n\n_pr_elco_and_worktype as(\n select\n ccop.tag_name, \n sum(case when pw.type = 'BUG' then commit_count else 0 end)/sum(commit_count) as cost_percentage\n from \n _commit_count_of_pr ccop\n left join _pr_worktype pw on ccop.pull_request_id = pw.pull_request_id\n GROUP BY 1\n)\n\nSELECT \n\tbot.tag_name,\n\tbot.bug_count,\n\tpeaw.cost_percentage as \"cost_percentage(bugfixing commits/total commits)\"\nFROM \n\t_bugs_of_tags bot\n\tjoin _pr_elco_and_worktype peaw on bot.tag_name = peaw.tag_name\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "2.1 Ratio of Bug Fix Commits [Last 5 Tags]", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "dev_eq" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts UNKNOWN" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts REQUIREMENT" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts BUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 9 - }, - "id": 55, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the severity distribution in bugs\n-- Get the work-type distribution in the last n tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_n_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 1\n),\n\nbugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/tags/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.type, i.title, i.description, i.severity \n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand i.type = 'BUG'\n)\n\nSELECT \n concat(biet.tag_name, \" \", case when biet.severity != '' then biet.severity else 'UNKNOWN' end) as severity,\n count(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nGROUP BY 1\n\n-- SELECT \n-- \t case when biet.severity != '' then biet.severity else 'UNKNOWN' end as severity,\n-- \tcount(*) as bug_count\n-- FROM \n-- \tbugs_in_each_tag biet\n-- GROUP BY biet.severity", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "2.2 Severity Distribution [Last Tag]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "dev_eq" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts UNKNOWN" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts REQUIREMENT" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts BUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 9 - }, - "id": 53, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the severity distribution in bugs\n-- Get the work-type distribution in the last n tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_n_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 1,1\n),\n\nbugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/tags/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.type, i.title, i.description, i.severity \n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand i.type = 'BUG'\n)\n\nSELECT \n concat(biet.tag_name, \" \", case when biet.severity != '' then biet.severity else 'UNKNOWN' end) as severity,\n count(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nGROUP BY 1\n\n-- SELECT \n-- \t case when biet.severity != '' then biet.severity else 'UNKNOWN' end as severity,\n-- \tcount(*) as bug_count\n-- FROM \n-- \tbugs_in_each_tag biet\n-- GROUP BY biet.severity", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "2.3 Severity Distribution [The Tag before Last]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "dev_eq" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts UNKNOWN" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts REQUIREMENT" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts BUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 9 - }, - "id": 51, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the severity distribution in bugs\n-- Get the work-type distribution in the last n tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_n_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 2,1\n),\n\nbugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/tags/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.type, i.title, i.description, i.severity \n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_n_tags)\n\t\tand i.type = 'BUG'\n)\n\nSELECT \n concat(biet.tag_name, \" \", case when biet.severity != '' then biet.severity else 'UNKNOWN' end) as severity,\n count(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nGROUP BY 1\n\n-- SELECT \n-- \t case when biet.severity != '' then biet.severity else 'UNKNOWN' end as severity,\n-- \tcount(*) as bug_count\n-- FROM \n-- \tbugs_in_each_tag biet\n-- GROUP BY biet.severity", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "2.4 Severity Distribution [The 2nd Tag before Last]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "displayMode": "auto" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "cost_percentage(bugfixing commits/total commits)" - }, - "properties": [ - { - "id": "unit", - "value": "percentunit" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "issue_key" - }, - "properties": [ - { - "id": "custom.width", - "value": 89 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "title" - }, - "properties": [ - { - "id": "custom.width", - "value": 457 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "assignee_name" - }, - "properties": [ - { - "id": "custom.width", - "value": 131 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "lead_time_in_days" - }, - "properties": [ - { - "id": "custom.width", - "value": 147 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "tag_name" - }, - "properties": [ - { - "id": "custom.width", - "value": 147 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "repo_name" - }, - "properties": [ - { - "id": "custom.width", - "value": 180 - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 24, - "x": 0, - "y": 16 - }, - "id": 43, - "options": { - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the number of fixed bugs in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n)\n\t\nselect distinct\n\tb.name as repo_name,\n\tSUBSTRING_INDEX(rid.new_ref_id,'tags/', -1) as tag_name, \n\ti.issue_key as issue_key,\n\ti.title,\n\ti.assignee_name,\n\ti.lead_time_minutes/1440 as lead_time_in_days,\n\tconcat(b.url,'/',i.issue_key) as url\nfrom\n\trefs_issues_diffs rid\n\tleft join issues i on rid.issue_id = i.id\n\tjoin boards b on SUBSTRING_INDEX(rid.new_ref_id,':', 3) = b.id\nwhere\n\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT SUBSTRING_INDEX(new_ref_id,':', -1) FROM _last_5_tags)\n\tand i.type = 'BUG'\norder by tag_name desc", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "2.5 List of Fixed Bugs [Last 5 Tags]", - "type": "table" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 11, - "x": 0, - "y": 23 - }, - "id": 30, - "options": { - "displayLabels": [ - "percent", - "name" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Component distribution of bugs fixed in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\nbugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.component, i.severity, i.title, i.description\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n)\n\n\nSELECT\n\tcase when component = '' then 'unlabeled' else 'labeled' end as component,\n\tcount(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nGROUP BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "3.1 Ratio of Issues with 'Component' Label [Last 5 Tags]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 13, - "x": 11, - "y": 23 - }, - "id": 31, - "options": { - "displayLabels": [ - "percent", - "name" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Component distribution of bugs fixed in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\nbugs_in_each_tag as(\n\tselect \n\t\tSUBSTRING_INDEX(rid.new_ref_id,'refs/', -1) as tag_name, \n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id,\n\t\ti.issue_key, i.component, i.severity, i.title, i.description\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n)\n\n\nSELECT\n\tcomponent,\n\tcount(*) as bug_count\nFROM \n\tbugs_in_each_tag biet\nwhere \n component != ''\nGROUP BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "3.2 Source of Bugs by Component [Last 5 Tags]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "yellow", - "value": 0.25 - }, - { - "color": "green", - "value": 0.4 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 30 - }, - "id": 23, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the % of contributors who fixed 80% of bugs in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_bugs as(\n\tselect \n\t\ti.issue_key, i.type, i.severity, i.title, i.description,\n\t\tpr.id, pr.author_name as pr_author, pr.created_date,\n\t\trank() over(partition by i.id order by pr.created_date asc) as pr_rank\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\t\tleft join pull_request_issues pri on i.id = pri.issue_id\n\t\tleft join pull_requests pr on pri.pull_request_id = pr.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n\torder by i.issue_key\n),\n\n_bug_fixed_count as(\n SELECT \n pr_author,\n count(*) bug_fixed_count\n FROM _bugs\n WHERE pr_rank = 1\n GROUP BY 1\n),\n\n_bug_fixed_count_running_total as(\n SELECT \n *, \n sum(bug_fixed_count) OVER (Order by bug_fixed_count desc) AS running_total\n FROM \n _bug_fixed_count\n),\n\n_percentile as(\n SELECT \n pr_author,\n bug_fixed_count,\n running_total/sum(bug_fixed_count) OVER () AS cumulative_percentage\n FROM \n _bug_fixed_count_running_total\n)\n\n\nSELECT \n count(case when cumulative_percentage <= 0.8 then pr_author else null end)/count(*) as \"% of contributors who fixed 80% of the bugs\"\nFROM _percentile", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "4.1 Contributor Fixing 80%+ Bugs [Last 5 Tags]", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Fixed Count", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 2 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 30 - }, - "id": 18, - "options": { - "barWidth": 0.5, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": { - "valueSize": 12 - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the bug fixer distribution in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_bugs as(\n\tselect \n\t\ti.issue_key, i.type, i.severity, i.title, i.description,\n\t\tpr.id, pr.author_name as pr_author, pr.created_date,\n\t\trank() over(partition by i.id order by pr.created_date asc) as pr_rank\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\t\tleft join pull_request_issues pri on i.id = pri.issue_id\n\t\tleft join pull_requests pr on pri.pull_request_id = pr.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n\torder by i.issue_key\n)\n\nSELECT \n pr_author,\n count(*) bug_fixed_count\nFROM _bugs\nWHERE pr_rank = 1\nGROUP BY 1\nORDER BY 2 desc\nlimit 10", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "4.2 Top Bug Fixers [Last 5 Tags]", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 0, - "y": 37 - }, - "id": 33, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the avg bug age in history\nselect \n avg(lead_time_minutes)/1440 as average_bug_age\nfrom issues\nleft join board_issues bi on issues.id = bi.issue_id\nwhere \n type = 'BUG'\n and status = 'DONE'\n and bi.board_id in ($repo_id)", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "5.1 Mean Bug Age in Days [All History]", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Bug Fixed Count", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 2 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 20, - "x": 4, - "y": 37 - }, - "id": 32, - "options": { - "barWidth": 0.7, - "groupWidth": 0.3, - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": { - "valueSize": 12 - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the bug age in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_bugs as(\n\tselect distinct\n\t\tSUBSTRING_INDEX(rid.new_ref_id,'tags/', -1) as tag_name,\n\t\ti.id,\n\t\ti.lead_time_minutes\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\t\tleft join pull_request_issues pri on i.id = pri.issue_id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n),\n\n_bugs_percentile as(\n select \n *,\n percent_rank() over (partition by tag_name order by lead_time_minutes) as percentile\n from _bugs order by 1\n),\n\n_avg_bug_age as(\n select \n tag_name,\n avg(lead_time_minutes)/1440 as average_bug_age\n from _bugs_percentile\n group by 1\n),\n\n_50th_bug_age as(\n select \n tag_name,\n min(lead_time_minutes)/1440 as \"50th_bug_age\"\n from _bugs_percentile\n where percentile >= 0.5\n group by 1\n)\n\nselect \n aba.*,\n eba.50th_bug_age\nfrom \n _avg_bug_age aba\n join _50th_bug_age eba on aba.tag_name = eba.tag_name", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "5.2 Mean + Median Bug Age Days [Last 5 Tags]", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 0, - "y": 43 - }, - "id": 34, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the 80th perccentile bug age in history\nwith _bugs_percentile as(\n select \n id,\n lead_time_minutes,\n percent_rank() over (order by lead_time_minutes) as percentile\n from issues\n where \n type = 'BUG'\n and status = 'DONE'\n)\n\nselect \n min(lead_time_minutes)/1440 as \"80th_bug_age\"\nfrom \n _bugs_percentile\nwhere \n percentile >= 0.5\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "5.3 Median Bug Age in Days [All History]", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "displayMode": "auto" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "key" - }, - "properties": [ - { - "id": "custom.width", - "value": 109 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "title" - }, - "properties": [ - { - "id": "custom.width", - "value": 725 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "name" - }, - "properties": [ - { - "id": "custom.width", - "value": 182 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "lead_time_in_days" - }, - "properties": [ - { - "id": "custom.width", - "value": 136 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "issue_key" - }, - "properties": [ - { - "id": "custom.width", - "value": 79 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "url" - }, - "properties": [ - { - "id": "custom.width", - "value": 784 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "tag_name" - }, - "properties": [ - { - "id": "custom.width", - "value": 150 - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 20, - "x": 4, - "y": 43 - }, - "id": 38, - "options": { - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the bug fixer distribution in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_bugs as(\n\tselect distinct\n\t b.name,\n\t\tSUBSTRING_INDEX(rid.new_ref_id,'tags/', -1) as tag_name,\n\t\ti.issue_key as issue_key,\n i.title,\n i.lead_time_minutes/1440 as lead_time_in_days,\n concat(b.url,'/',i.issue_key) as url\n\tfrom\n\t\trefs_issues_diffs rid\n\t\tleft join issues i on rid.issue_id = i.id\n\t\tleft join pull_request_issues pri on i.id = pri.issue_id\n\t\tjoin boards b on SUBSTRING_INDEX(rid.new_ref_id,':', 3) = b.id\n\twhere\n\t\tSUBSTRING_INDEX(rid.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rid.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand i.type = 'BUG'\n),\n\n_bug_age_rank as(\n select \n *,\n row_number() over (partition by tag_name order by lead_time_in_days desc) as bug_age_rank\n from _bugs\n)\n\nselect \n name,\n tag_name,\n issue_key,\n title,\n lead_time_in_days,\n url\nfrom _bug_age_rank\nwhere bug_age_rank <=10 \norder by tag_name, lead_time_in_days desc", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "5.4 List of Long-Lead Bugs [Closed Bugs in Last 5 Tags]", - "type": "table" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [], - "unit": "short" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "bug_count" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "=avg_bug_age" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 49 - }, - "id": 35, - "options": { - "displayLabels": [ - "name", - "percent" - ], - "legend": { - "displayMode": "hidden", - "placement": "bottom", - "values": [ - "value", - "percent" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the avg bug age in history\nwith _avg_bug_age as(\n select \n type,\n avg(lead_time_minutes) as average_bug_age\n from \n issues\n left join board_issues bi on issues.id = bi.issue_id\n where \n type = 'BUG'\n and status = 'DONE'\n and bi.board_id in ($repo_id)\n group by 1\n),\n\n\n_bug_queue_time as(\n select \n i.id,\n abg.average_bug_age,\n TIMESTAMPDIFF(MINUTE,created_date,NOW()) as queue_time,\n case when TIMESTAMPDIFF(MINUTE,created_date,NOW()) >= average_bug_age then \">=avg_bug_age\" else \" Mean]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "displayMode": "auto" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "=avg_bug_age" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "issue_key" - }, - "properties": [ - { - "id": "custom.width", - "value": 81 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "title" - }, - "properties": [ - { - "id": "custom.width", - "value": 535 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "created_date" - }, - "properties": [ - { - "id": "custom.width", - "value": 149 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "queue_time_in_days" - }, - "properties": [ - { - "id": "custom.width", - "value": 140 - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 18, - "x": 6, - "y": 49 - }, - "id": 39, - "options": { - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the queue time of all backlog bugs\nselect \n b.name as repo_name,\n i.issue_key as issue_key,\n i.title,\n i.created_date,\n (TIMESTAMPDIFF(MINUTE, i.created_date,NOW()))/3600 as queue_time_in_days,\n concat(b.url,'/',i.issue_key) as url\nfrom \n issues i\n left join board_issues bi on i.id = bi.issue_id\n left join boards b on bi.board_id = b.id\nwhere\n i.type = 'BUG'\n and i.status != 'DONE'\n and b.id in ($repo_id)\norder by queue_time_in_days desc", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "5.6 List of Outstanding Bugs [All Open Bugs]", - "type": "table" - }, - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 55 - }, - "id": 47, - "panels": [], - "title": "Contribution", - "type": "row" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 56 - }, - "id": 41, - "options": { - "barWidth": 0.3, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "orientation": "horizontal", - "showValue": "auto", - "text": { - "valueSize": 12 - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "-- Get the bug distribution in last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 10\n)\n\nselect \n\tSUBSTRING_INDEX(rcd.new_ref_id,'refs/tags/', -1) as tag_name,\n\tSUBSTRING_INDEX(rcd.old_ref_id,'refs/tags/', -1) as old_tag_name,\n\tcount(*) as commit_count\nfrom\n\trefs_commits_diffs rcd\n\tleft join commits c on rcd.commit_sha = c.sha\nwhere\n\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t-- and rcd.new_ref_id in (select new_ref_id from _last_5_tags)\n\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\ngroup by 1,2\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "6.1 Number of New Commits Per Tag", - "type": "barchart" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "displayMode": "auto" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "message" - }, - "properties": [ - { - "id": "custom.width", - "value": 395 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "additions" - }, - "properties": [ - { - "id": "custom.width", - "value": 92 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "deletions" - }, - "properties": [ - { - "id": "custom.width", - "value": 86 - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 56 - }, - "id": 42, - "options": { - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "-- Get the bug distribution in last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 10\n)\n\nselect \n\tSUBSTRING_INDEX(rcd.new_ref_id,'refs/tags/', -1) as new_tag_name,\n\tSUBSTRING_INDEX(rcd.old_ref_id,'refs/tags/', -1) as compared_tag_name,\n\tc.sha,\n\tc.message,\n\tc.additions,\n\tc.deletions,\n\tc.author_name\nfrom\n\trefs_commits_diffs rcd\n\tleft join commits c on rcd.commit_sha = c.sha\nwhere\n\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t-- and rcd.new_ref_id in (select new_ref_id from _last_5_tags)\n\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\norder by 1 desc", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "6.2 Commit List for Drill-Down [Last 5 Tags]", - "type": "table" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "dev_eq" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts UNKNOWN" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts REQUIREMENT" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.3.2.2-lts BUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 63 - }, - "id": 26, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the work-type distribution in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 1\n),\n\n_combine_pr as (\n select pull_request_id as id, commit_sha, p.pull_request_key as pull_request_key from pull_request_commits left join pull_requests p on pull_request_commits.pull_request_id = p.id\n where base_repo_id in ($repo_id)\n union\n select id, merge_commit_sha, pull_request_key as commit_sha from pull_requests where base_repo_id in ($repo_id)\n),\n\n_commit_count_of_pr as(\n select\n SUBSTRING_INDEX(rcd.new_ref_id,'tags/', -1) as tag_name,\n pr.id as pull_request_id,\n -- count(c.sha) as pr_commit_count\n sum(c.dev_eq) as pr_eloc\n FROM \n refs_commits_diffs rcd\n\t\tleft join commits c on rcd.commit_sha = c.sha\n\t\t-- left join pull_request_commits prc on c.sha = prc.commit_sha\n\t\tleft join _combine_pr pr on c.sha = pr.commit_sha\n\twhere\n\t\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\tgroup by 1,2\n),\n\n_pr_issues as(\n select\n pri.pull_request_id,\n pri.issue_id,\n i.issue_key,\n i.type,\n row_number() over(partition by issue_id ORDER by pr.created_date asc) as pr_rank\n from\n pull_request_issues pri\n left join pull_requests pr on pri.pull_request_id = pr.id\n left join issues i on pri.issue_id = i.id\n where \n i.issue_key != 0\n),\n\n_final_results as(\n select\n distinct ccop.*, pi.type\n from \n _commit_count_of_pr ccop\n left join _pr_issues pi on ccop.pull_request_id = pi.pull_request_id\n where pi.pr_rank = 1\n order by 1\n)\n\nSELECT\n tag_name,\n case when type != '' then type else 'UNKNOWN' end as type,\n -- sum(pr_commit_count) as commit_count\n sum(pr_eloc) as dev_eq\nfrom _final_results\ngroup by 1,2", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "7.1 Work-Type Distribution [Last Tag]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 63 - }, - "id": 36, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the work-type distribution in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 1,1\n),\n\n\n_combine_pr as (\n select pull_request_id as id, commit_sha, p.pull_request_key as pull_request_key from pull_request_commits left join pull_requests p on pull_request_commits.pull_request_id = p.id\n where base_repo_id in ($repo_id)\n union\n select id, merge_commit_sha, pull_request_key as commit_sha from pull_requests where base_repo_id in ($repo_id)\n),\n\n_commit_count_of_pr as(\n select\n SUBSTRING_INDEX(rcd.new_ref_id,'tags/', -1) as tag_name,\n pr.id as pull_request_id,\n -- count(c.sha) as pr_commit_count\n sum(c.dev_eq) as pr_eloc\n FROM \n refs_commits_diffs rcd\n\t\tleft join commits c on rcd.commit_sha = c.sha\n\t\t-- left join pull_request_commits prc on c.sha = prc.commit_sha\n\t\tleft join _combine_pr pr on c.sha = pr.commit_sha\n\twhere\n\t\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\tgroup by 1,2\n),\n\n_pr_issues as(\n select\n pri.pull_request_id,\n pri.issue_id,\n i.issue_key,\n i.type,\n row_number() over(partition by issue_id ORDER by pr.created_date asc) as pr_rank\n from\n pull_request_issues pri\n left join pull_requests pr on pri.pull_request_id = pr.id\n left join issues i on pri.issue_id = i.id\n where \n i.issue_key != 0\n),\n\n_final_results as(\n select\n distinct ccop.*, pi.type\n from \n _commit_count_of_pr ccop\n left join _pr_issues pi on ccop.pull_request_id = pi.pull_request_id\n where pi.pr_rank = 1\n order by 1\n)\n\nSELECT\n tag_name,\n case when type != '' then type else 'UNKNOWN' end as type,\n -- sum(pr_commit_count) as commit_count\n sum(pr_eloc) as dev_eq\nfrom _final_results\ngroup by 1,2", - "refId": "A", - "select": [ - [ - { - "params": [ - "script_version" - ], - "type": "column" - } - ] - ], - "table": "_devlake_migration_history", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "7.2 Work-Type Distribution [The Tag before Last]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "mappings": [] - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "v22.1.4.30-stable BUG" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "v22.1.4.30-stable REQUIREMENT" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 63 - }, - "id": 37, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "donut", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get the work-type distribution in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 2,1\n),\n\n\n_combine_pr as (\n select pull_request_id as id, commit_sha, p.pull_request_key as pull_request_key from pull_request_commits left join pull_requests p on pull_request_commits.pull_request_id = p.id\n where base_repo_id in ($repo_id)\n union\n select id, merge_commit_sha, pull_request_key as commit_sha from pull_requests where base_repo_id in ($repo_id)\n),\n\n_commit_count_of_pr as(\n select\n SUBSTRING_INDEX(rcd.new_ref_id,'tags/', -1) as tag_name,\n pr.id as pull_request_id,\n -- count(c.sha) as pr_commit_count\n sum(c.dev_eq) as pr_eloc\n FROM \n refs_commits_diffs rcd\n\t\tleft join commits c on rcd.commit_sha = c.sha\n\t\t-- left join pull_request_commits prc on c.sha = prc.commit_sha\n\t\tleft join _combine_pr pr on c.sha = pr.commit_sha\n\twhere\n\t\tSUBSTRING_INDEX(rcd.new_ref_id,':', 3) in ($repo_id)\n\t\t-- and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\t\tand SUBSTRING_INDEX(rcd.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n\tgroup by 1,2\n),\n\n_pr_issues as(\n select\n pri.pull_request_id,\n pri.issue_id,\n i.issue_key,\n i.type,\n row_number() over(partition by issue_id ORDER by pr.created_date asc) as pr_rank\n from\n pull_request_issues pri\n left join pull_requests pr on pri.pull_request_id = pr.id\n left join issues i on pri.issue_id = i.id\n where \n i.issue_key != 0\n),\n\n_final_results as(\n select\n distinct ccop.*, pi.type\n from \n _commit_count_of_pr ccop\n left join _pr_issues pi on ccop.pull_request_id = pi.pull_request_id\n where pi.pr_rank = 1\n order by 1\n)\n\nSELECT\n tag_name,\n case when type != '' then type else 'UNKNOWN' end as type,\n -- sum(pr_commit_count) as commit_count\n sum(pr_eloc) as dev_eq\nfrom _final_results\ngroup by 1,2", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "7.3 Work-Type Distribution [The 2nd Tag before Last]", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "#EAB839", - "value": 0.25 - }, - { - "color": "green", - "value": 0.4 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 70 - }, - "id": 27, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "-- Get each contributor's work in bugfixing in the last 5 tags\nwith refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n),\n\n_author_commits as(\n SELECT \n \tc.author_name,\n -- count(c.sha) as commit_count\n sum(c.dev_eq) total_dev_eq\n FROM \n refs_commits_diffs rcf\n -- left join commits c on rcf.commit_sha = c.sha\n left join commits c on rcf.commit_sha = c.dev_eq\n WHERE\n \t-- rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n \tSUBSTRING_INDEX(rcf.new_ref_id,':', 3) in ($repo_id)\n \tand SUBSTRING_INDEX(rcf.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\n GROUP BY 1\n),\n\n_author_commits_running_total as(\n SELECT \n *, \n -- sum(commit_count) OVER (Order by commit_count desc) AS running_total\n sum(total_dev_eq) OVER (Order by total_dev_eq desc) AS running_total\n FROM \n _author_commits\n),\n\n_percentile as(\n SELECT \n author_name,\n -- commit_count,\n -- running_total/sum(commit_count) OVER () AS cumulative_percentage\n total_dev_eq,\n running_total/sum(total_dev_eq) OVER () AS cumulative_percentage\n FROM \n _author_commits_running_total\n)\n\n\nSELECT \n count(case when cumulative_percentage <= 0.8 then author_name else null end)/count(*) as \"contributors who contributed 80% of dev_eq\"\nFROM _percentile", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "8.1 Committers Contributing 80%+ Commits [Last 5 Tags]", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Dev Equivalent", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 6, - "y": 70 - }, - "id": 3, - "options": { - "barWidth": 0.3, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with refs_commits_diffs as(\n SELECT\n new_refs.id as new_ref_id, old_refs.id as old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha\n FROM\n commits_diffs\n LEFT JOIN refs new_refs on new_refs.commit_sha = commits_diffs.new_commit_sha\n LEFT JOIN refs old_refs on old_refs.commit_sha = commits_diffs.old_commit_sha\n),\n\n_last_5_tags as(\n SELECT \n -- distinct new_ref_id, old_ref_id\n distinct SUBSTRING_INDEX(new_ref_id,':', -1) as new_ref_id, SUBSTRING_INDEX(old_ref_id,':', -1) as old_ref_id\n FROM \n refs_commits_diffs\n WHERE\n\t\tSUBSTRING_INDEX(new_ref_id,':', 3) in ($repo_id)\n\tORDER BY 1 desc\n\tLIMIT 5\n)\n\n\nSELECT \n\tc.author_name,\n -- count(c.sha) total_dev_eq\n sum(c.dev_eq) total_dev_eq\nFROM \n refs_commits_diffs rcf\n -- left join commits c on rcf.commit_sha = c.sha\n left join commits c on rcf.commit_sha = c.dev_eq\nWHERE\n\t-- rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags)\n\tSUBSTRING_INDEX(rcf.new_ref_id,':', 3) in ($repo_id)\n\tand SUBSTRING_INDEX(rcf.new_ref_id,':', -1) in (SELECT new_ref_id FROM _last_5_tags)\nGROUP BY 1\nORDER BY 2 desc\nlimit 10", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "table": "ae_projects", - "timeColumn": "ae_create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "8.2 Top Contributors [Last 5 Tags]", - "type": "barchart" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "mysql", - "definition": "select concat(name, '--', id) as text from repos", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Repo", - "multi": true, - "name": "repo_id", - "options": [], - "query": "select concat(name, '--', id) as text from repos", - "refresh": 1, - "regex": "/^(?.*)--(?.*)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "EE_GitHub_Release_Quality_and_Contribution_Analysis", - "uid": "2xuOaQUnk5", - "version": 4 -} \ No newline at end of file diff --git a/grafana/_archive/GithubDomainLayerOnly.json b/grafana/_archive/GithubDomainLayerOnly.json deleted file mode 100644 index d15048c482a..00000000000 --- a/grafana/_archive/GithubDomainLayerOnly.json +++ /dev/null @@ -1,622 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 10, - "links": [], - "panels": [ - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 0, - "y": 0 - }, - "id": 10, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n sum(additions)\nFROM commits\nWHERE\n $__timeFilter(created_at)\n AND message not like '%Merge branch%'", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Added LOC", - "type": "stat" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 4, - "y": 0 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n sum(deletions)\nFROM commits\nWHERE\n $__timeFilter(created_at) \n AND message not like '%Merge branch%'\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Deleted Loc", - "type": "stat" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 13, - "w": 12, - "x": 8, - "y": 0 - }, - "id": 14, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with loc as(\n SELECT\n DATE_ADD(date(authored_date), INTERVAL 1 DAY) as time,\n sum(additions) as added_LOC,\n sum(deletions) as deleted_LOC\n FROM commits\n WHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n group by 1\n)\n\nSELECT \n timestamp(time) as time,\n added_LOC as \"Added LOC\",\n deleted_LOC as \"Deleted LOC\"\nFROM loc\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Added and Deleted LOC Over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 6 - }, - "id": 8, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n count(distinct author_email) as developer_count\nFROM commits", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Total Developers By Email", - "type": "stat" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 6 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM \n prs\nWHERE\n $__timeFilter(created_at) AND state = 'open'\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Open Pull Requests", - "type": "stat" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 0, - "y": 13 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM \n prs\nWHERE\n $__timeFilter(created_at)\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Total Pull Request Count", - "type": "stat" - }, - { - "datasource": "mysql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 4, - "x": 4, - "y": 13 - }, - "id": 2, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n count(sha)\n \nFROM commits\nWHERE\n $__timeFilter(created_at)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "duration_sec" - ], - "type": "column" - } - ] - ], - "table": "builds", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Total Commits", - "type": "stat" - } - ], - "schemaVersion": 30, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "2021-01-30T18:24:57.517Z", - "to": "2022-02-02T18:24:57.519Z" - }, - "timepicker": {}, - "timezone": "", - "title": "Github Domain Layer Only", - "uid": "ilRLEaFnz", - "version": 5 -} diff --git a/grafana/_archive/Gitlab.json b/grafana/_archive/Gitlab.json deleted file mode 100644 index 806613efbf0..00000000000 --- a/grafana/_archive/Gitlab.json +++ /dev/null @@ -1,2186 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 10, - "iteration": 1638297448523, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": false, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "Data Source Specific Dashboard" - ], - "targetBlank": false, - "title": "Metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 48, - "options": { - "content": "
\n
\n \"No.1\"\n

MR Throughput and Pass Rate

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of Pull/Merge request created.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 4 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM \n gitlab_merge_requests gmr\nWHERE\n gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Merge Request Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Total number of Pull/Merge request merged.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 4 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n count(*) as value\nFROM\n gitlab_merge_requests gmr\nWHERE\n gmr.project_id = $repo_id\n and state = 'merged'\n and $__timeFilter(gitlab_created_at)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Merge Request Pass Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Pull Request Pass Count/Pull Request Count", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 0.8 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 4 - }, - "id": 37, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n 1.0 * count(case when state = 'merged' then 1 else null end)/count(*)\nFROM\n gitlab_merge_requests gmr\nWHERE\n gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Merge Request Pass Rate (%)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Pull Request Pass Rate over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", value \"pull_request_pass_rate\" of \"2021-06-01\" calculates the PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _mr_request_rate as(\n SELECT\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n state\n FROM\n gitlab_merge_requests gmr\n WHERE\n gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\n)\n\nSELECT \n timestamp(time) as time,\n 1.0 * count(case when state = 'merged' then 1 else null end)/count(*) as \"Pull Request Pass Rate\"\nFROM _mr_request_rate\nGROUP BY 1\nORDER BY 1\n", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Merge Request Pass Rate over Time", - "type": "timeseries" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 16 - }, - "id": 50, - "options": { - "content": "
\n
\n \"No.2\"\n

MR Review Metrics

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Average PR/MR review time.\n2. PR/MR review time refers to the time between the creation time and merged time of a PR/MR.\n3. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 3 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 20 - }, - "id": 80, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \n now() as time,\n avg(TIMESTAMPDIFF(MINUTE,gitlab_created_at,merged_at))/1440 as value\nfrom gitlab_merge_requests\nWHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Avg Merge Request Review Time (day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The 80th percentile PR/MR review time.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 7 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 20 - }, - "id": 83, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _ranks as(\n select \n TIMESTAMPDIFF(SECOND,gitlab_created_at,merged_at)/86400 as metric,\n percent_rank() over (order by TIMESTAMPDIFF(MINUTE,gitlab_created_at,merged_at) asc) as ranks\n from gitlab_merge_requests\n WHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n )\n \nselect\n now() as time,\n max(metric) as value\nfrom _ranks\nwhere \n ranks <= 0.8\ngroup by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "80th Percentile Merge Request Review Time(day)", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. The average and 80th percentile PR/MR review time over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", the average review time of \"2021-06-01\" refers to the review time of PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Review Time (day)", - "axisPlacement": "auto", - "axisSoftMin": -3, - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 20 - }, - "id": 82, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with reviewed_mr as(\n select\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n TIMESTAMPDIFF(SECOND,first_comment_time,merged_at)/86400 as review_time\n from gitlab_merge_requests\n WHERE\n merged_at is not null\n and $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n)\n\nselect \n timestamp(time) as time,\n avg(review_time) as \"Average Review Time\"\nfrom reviewed_mr\ngroup by 1\norder by 1 asc", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Merge Request Review Time over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The average round of PR/MR review.", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 26 - }, - "id": 53, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n avg(review_rounds) as value\nFROM \n gitlab_merge_requests gmr\nWHERE\n state = 'merged'\n and review_rounds > 0\n and gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Merge Request Review Round", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Avg PR/MR Review Round over time.\n2. The time granularity can be switched to week or month by \"Time Interval\" above. \n3. When Time Interval is set to \"month\", value \"Avg Pull Request Review Round\" of \"2021-06-01\" calculates the PR/MR whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 26 - }, - "id": 55, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "SELECT\n timestamp(DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY)) as time,\n avg(review_rounds) as \"Pull Request Review Round\"\nFROM\n gitlab_merge_requests gmr\nWHERE\n state = 'merged'\n and review_rounds > 0\n and gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\nGROUP BY 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Merge Request Review Round over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The average round of PR/MR review of PR authors.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 32 - }, - "id": 54, - "options": { - "barWidth": 0.52, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n author_username as Author,\n avg(review_rounds) as value\nFROM \n gitlab_merge_requests gmr\nWHERE\n state = 'merged'\n and review_rounds > 0\n and gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\nGROUP BY 1,2\nORDER BY 3 desc\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Average Merge Request Review Round by Author", - "type": "barchart" - }, - { - "datasource": "mysql", - "description": "One-time pass rate for PR/MR over time.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "One-time Pass Rate(%)", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 38 - }, - "id": 56, - "options": { - "legend": { - "calcs": [], - "displayMode": "hidden", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "with _review_rounds as(\n SELECT\n DATE_ADD(date(gitlab_created_at), INTERVAL -$interval(date(gitlab_created_at))+1 DAY) as time,\n review_rounds\n FROM\n gitlab_merge_requests gmr\n WHERE\n state = 'merged'\n and review_rounds > 0\n and gmr.project_id = $repo_id\n and $__timeFilter(gitlab_created_at)\n)\n\nSELECT \n timestamp(time) as time,\n count(case when review_rounds = 1 then true else null end)/count(*) as one_time_pass_rate\nFROM \n _review_rounds\nGROUP BY 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "value" - ], - "type": "column" - } - ] - ], - "timeColumn": "time", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "One-time Pass Rate for Code Reviews over Time", - "type": "timeseries" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 44 - }, - "id": 52, - "options": { - "content": "
\n
\n \"No.3\"\n

Commit Metrics

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Total number of commits created.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "decimals": 1, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 48 - }, - "id": 34, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS \"time\",\n count(*) as value\nFROM gitlab_commits\nWHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of commits created over time.\n2. When Time Interval is set to \"month\", the commit_count\" of \"2021-06-01\" calculates the commits whose creation time falls under [2020-06-01, 2020-07-01)", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "Commit Count", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 6, - "y": 48 - }, - "id": 36, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _commits as(\n SELECT\n DATE_ADD(date(authored_date), INTERVAL -$interval(date(authored_date))+1 DAY) as time,\n count(*) as commit_count\n FROM gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\n group by 1\n)\n\nSELECT \n timestamp(time) as time,\n commit_count as \"Commit Count\"\nFROM _commits\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Count over Time", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "The number of commits from different repos.", - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 6, - "x": 18, - "y": 48 - }, - "hiddenSeries": false, - "id": 87, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n gp.name as metric,\n count(*) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id\nWHERE\n $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1,2\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Commit Count by Repo", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "series", - "name": null, - "show": true, - "values": [ - "total" - ] - }, - "yaxes": [ - { - "$$hashKey": "object:89", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:90", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "datasource": "mysql", - "description": "1. Sum of the number of added lines of code under all commits.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 0, - "y": 54 - }, - "id": 42, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n sum(additions) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id \nWHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added LOC", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Sum of the number of deleted lines of code under all commits.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner) and \"Gitlab repo\"(\"Choose Repo\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 6, - "y": 54 - }, - "id": 43, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n sum(deletions) as value\nFROM gitlab_commits gc\n join gitlab_projects gp on gc.project_id = gp.gitlab_id \nWHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Deleted LOC", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Added/deleted number of lines of code over time.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "LOC", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 54 - }, - "id": 47, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with loc as(\n SELECT\n DATE_ADD(date(authored_date), INTERVAL -$interval(date(authored_date))+1 DAY) as time,\n sum(additions) as added_LOC,\n sum(deletions) as deleted_LOC\n FROM gitlab_commits\n WHERE\n message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and project_id = $repo_id\n group by 1\n)\n\nSELECT \n timestamp(time) as time,\n added_LOC as \"Added LOC\",\n deleted_LOC as \"Deleted LOC\"\nFROM loc\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added and Deleted LOC over Time", - "type": "timeseries" - }, - { - "datasource": "mysql", - "description": "The number of added/deleted LOC from different commit authors.", - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "orange", - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "LOC", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 60 - }, - "id": 49, - "options": { - "barWidth": 0.9, - "groupWidth": 0.6, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "orientation": "auto", - "showValue": "auto", - "text": {}, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() as time,\n sum(additions) as \"Added LOC\",\n sum(deletions) as \"Removed LOC\",\n gc.author_name\nFROM gitlab_commits gc \n join gitlab_projects gp on gc.project_id = gp.gitlab_id\nWHERE\n author_name is not null\n and message not like '%Merge branch%'\n and $__timeFilter(authored_date)\n and gc.project_id = $repo_id\ngroup by 1,4\norder by 2 desc\nlimit 20", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Added and Deleted LOC by Author", - "type": "barchart" - }, - { - "datasource": null, - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 66 - }, - "id": 97, - "options": { - "content": "
\n
\n \"No.4\"\n

Development Cost Metrics

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "queryType": "randomWalk", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": "mysql", - "description": "1. Number of people who have created or reviewed a Pull/Merge Request.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 70 - }, - "id": 90, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with all_developers as(\n select \n gitlab_reviewers.name as user_name\n from gitlab_merge_requests\n join gitlab_reviewers on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\n\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_merge_requests.project_id = $repo_id\n union\n select \n distinct author_username as user_name \n from gitlab_merge_requests\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_merge_requests.project_id = $repo_id\n union\n select \n distinct author_name as user_name \n from gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and gitlab_commits.project_id = $repo_id\n)\n\n\nSELECT\n now() AS \"time\",\n count(distinct user_name) as developer_count\nFROM all_developers", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Total Developer Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Total number of Commit Authors.\n2. The commits being calculated are filtered by \"authored_date\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 76 - }, - "id": 92, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n count(distinct author_name) as value\nFROM gitlab_commits\nWHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\ngroup by 1\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Commit Author Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "1. Number of Pull/Merge Request Reviewers.\n2. The PR/MR being calculated are filtered by \"PR/MR creation time\" (time filter at the upper-right corner).", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 76 - }, - "id": 94, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select \n now() as time,\n count(distinct name)\nfrom\n gitlab_reviewers", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Reviewer Count", - "type": "stat" - }, - { - "datasource": "mysql", - "description": "Pull Request Reviewer Count/Total Developer Count", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "green", - "value": 0.2 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 76 - }, - "id": 96, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with all_user_names as(\n select \n distinct gitlab_reviewers.name as user_name\n from gitlab_reviewers\n join gitlab_merge_requests on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\n WHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_reviewers.project_id = $repo_id\n union\n select \n distinct author_username as user_name \n from gitlab_merge_requests\n WHERE\n $__timeFilter(gitlab_created_at)\n and project_id = $repo_id\n union\n select \n distinct author_name as user_name \n from gitlab_commits\n WHERE\n $__timeFilter(authored_date)\n and project_id = $repo_id\n),\n\nreviewer as (\n select distinct gitlab_reviewers.name from gitlab_reviewers\n join gitlab_merge_requests on gitlab_reviewers.merge_request_id = gitlab_merge_requests.gitlab_id\nWHERE\n $__timeFilter(gitlab_merge_requests.gitlab_created_at)\n and gitlab_reviewers.project_id = $repo_id\n)\n\nSELECT\n now() AS \"time\",\n 1.0*count(distinct reviewer.name)/count(distinct user_name) as value\nFROM reviewer, all_user_names", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pull Request Reviewer Rate (%)", - "type": "stat" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "Gitlab", - "Data Source Specific Dashboard" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": false, - "text": "Month", - "value": "DAY" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Time Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "Week", - "value": "DAYOFWEEK" - }, - { - "selected": true, - "text": "Month", - "value": "DAY" - } - ], - "query": "Week : DAYOFWEEK, Month : DAY", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "allValue": null, - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "mysql", - "definition": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Choose Repo", - "multi": false, - "name": "repo_id", - "options": [], - "query": "select distinct concat(name, ': ', gitlab_id) from gitlab_projects", - "refresh": 1, - "regex": "/^(?[^:]+): (?\\d+)$/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Gitlab", - "uid": "6YcsxLN7z", - "version": 2 -} diff --git a/grafana/_archive/Jenkins.json b/grafana/_archive/Jenkins.json deleted file mode 100644 index 049b88e5a6a..00000000000 --- a/grafana/_archive/Jenkins.json +++ /dev/null @@ -1,470 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 5, - "iteration": 1637051361097, - "links": [ - { - "asDropdown": false, - "icon": "bolt", - "includeVars": false, - "keepTime": true, - "tags": [], - "targetBlank": false, - "title": "Homepage", - "tooltip": "", - "type": "link", - "url": "/grafana/d/Lv1XbLHnk/data-specific-dashboards-homepage" - }, - { - "asDropdown": false, - "icon": "external link", - "includeVars": false, - "keepTime": true, - "tags": [ - "Data Source Specific Dashboard" - ], - "targetBlank": false, - "title": "metric dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "datasource": "mysql", - "description": "1. Number of builds executed.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 0 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n count(*)\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Count", - "type": "stat" - }, - { - "cacheTimeout": null, - "datasource": "mysql", - "description": "The percentage of successful, failed, and aborted builds.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 0 - }, - "id": 37, - "interval": null, - "links": [], - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "values": [ - "percent", - "value" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": true - }, - "tooltip": { - "mode": "single" - } - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "table", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n result as metric,\n count(*) as value\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nGROUP BY 1,2\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Result Distribution", - "type": "piechart" - }, - { - "datasource": "mysql", - "description": "Number of successful builds / Number of total builds", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 6 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "SELECT\n now() AS time,\n 1.0 * count(case when result = 'SUCCESS' then 1 else null end)/count(*)\nFROM jenkins_builds\nWHERE\n $__timeFilter(start_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "project_id" - ], - "type": "column" - } - ] - ], - "table": "gitlab_commits", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Build Success Rate", - "type": "stat" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "mysql", - "description": "1. Build success rate over time.\n2. The builds being calculated are filtered by \"build starting time\" (time filter at the upper-right corner) and \"Jira board\" (\"Choose Board\" filter at the upper-left corner)", - "fieldConfig": { - "defaults": { - "unit": "percentunit" - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 16, - "x": 8, - "y": 6 - }, - "hiddenSeries": false, - "id": 50, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.0.6", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": true, - "rawSql": "with _build_success_rate as(\r\n SELECT\r\n DATE_ADD(date(start_time), INTERVAL -$interval(date(start_time))+1 DAY) as time,\r\n result\r\n FROM\r\n jenkins_builds\r\n WHERE\r\n $__timeFilter(start_time)\r\n)\r\n\r\nSELECT \r\n timestamp(time) as time,\r\n 1.0 * sum(case when result = 'SUCCESS' then 1 else 0 end)/ count(*) as \"Build Success Rate\"\r\nFROM _build_success_rate\r\ngroup by 1\r\norder by 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Build Success Rate over Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:262", - "decimals": null, - "format": "percentunit", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "$$hashKey": "object:263", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "Jenkins", - "Data Source Specific Dashboard" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": true, - "text": "Month", - "value": "DAY" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Time Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "Week", - "value": "DAYOFWEEK" - }, - { - "selected": true, - "text": "Month", - "value": "DAY" - } - ], - "query": "Week : DAYOFWEEK, Month : DAY", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Jenkins", - "uid": "zDsUxYHnz", - "version": 13894 -} diff --git a/grafana/_archive/UserValueSpecificDashboardsHomepage.json b/grafana/_archive/UserValueSpecificDashboardsHomepage.json deleted file mode 100644 index 4c1be1f9b2d..00000000000 --- a/grafana/_archive/UserValueSpecificDashboardsHomepage.json +++ /dev/null @@ -1,344 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 4, - "links": [], - "panels": [ - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 0, - "y": 0 - }, - "id": 10, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"velocity\"\n

Delivery Velocity

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 7, - "y": 0 - }, - "id": 5, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"quality\"\n

Delivery Quality

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "datasource": null, - "gridPos": { - "h": 18, - "w": 10, - "x": 14, - "y": 0 - }, - "id": 9, - "links": [], - "options": { - "content": "![Merico Logo](/public/img/lake/logo.png \"Merico\")\n***\n# **MARI Introduction**\n***\nMARI is a methodology developed specifically to bring the process of data driven software engineering to organizations of all sizes in a simple and organized way; empowering them to translate measurement into insight, and insight into results. MARI is a cycle of iterations, focused on mapping challenges to metrics, metrics to actions, and actions to improvements. \n

\n\n\n#### *M (Measure)*\n \nGreat answers require great questions. To get started, identify specific and clearly definable pain points in your development processes or teams, set tightly constrained goals, and then with team consensus, prioritize those goals. From here, identify 'North Star' metrics and measurements that map to each goal. Once you have clearly defined and documented the pain-points, mapped them to metrics, and prioritized, it's time to standardize these metrics and methods across teams. \n\n##### *A (Analyze)*\n\nEffective measures are only as good as the analysis that gets applied to them. To understand productivity in an effective and quantitative way, look towards key attributes like trend, distribution, variability, dispersion, and periodicity. These analysis techniques can uncover problems and opportunities. \n\n##### *R (Review)*\n\nOnce you have equipped yourself with the results of the analysis, it's time to coherently review them and discover what hinders your team from reaching full potential. With key stakeholders in the conversation, assess your workflow, toolset, developer satisfaction and many other aspects with an eye towards identifying cause and effect. It is critical that this part of the process is focused on the real root causes, not blame. Review should involve those affected, and those with the responsibility. \n\n##### *I (Improve)*\n\nAfter review, it's time to prepare a defined and clear iteration. What actions can be taken to potentially address the areas of opportunity? These actions should be clear, defined, measurable, and focused enough that success or failure can be clearly identified and attributed. In larger organizations, consider A/B testing the action plan between a few teams, and be sure to test and assess action plans at smaller scale before implementing enterprise-wide. \n \nAs you implement action plans for improvement, refresh your MARI cycle to coherently drive progress and evolution.\n\n", - "mode": "markdown" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 0, - "y": 9 - }, - "id": 7, - "interval": null, - "links": [], - "options": { - "content": " \n
\n \"cost\"\n

Delivery Cost

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "gridPos": { - "h": 9, - "w": 7, - "x": 7, - "y": 9 - }, - "id": 13, - "interval": null, - "links": [], - "options": { - "content": " \n
\n \"capability\"\n

Delivery Capability

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "type": "text" - }, - { - "cacheTimeout": null, - "datasource": null, - "description": "", - "gridPos": { - "h": 9, - "w": 7, - "x": 0, - "y": 18 - }, - "id": 12, - "interval": null, - "links": [], - "options": { - "content": "\n
\n \"requirement\"\n

New Dashboard

\n
\n
", - "mode": "html" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "format": "time_series", - "group": [], - "metricColumn": "none", - "rawQuery": false, - "rawSql": "SELECT\n create_time AS \"time\",\n progress\nFROM ca_analysis\nWHERE\n $__timeFilter(create_time)\nORDER BY 1", - "refId": "A", - "select": [ - [ - { - "params": [ - "progress" - ], - "type": "column" - } - ] - ], - "table": "ca_analysis", - "timeColumn": "create_time", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "transparent": true, - "type": "text" - } - ], - "refresh": "", - "schemaVersion": 30, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "User Value Specific Dashboards-Homepage", - "uid": "RXJZNpMnz", - "version": 14 -} From 2820129c88a0b5e7bca29ac20b01d14abce311b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:55:53 -0300 Subject: [PATCH 05/11] refactor(grafana): rename mysql dashboard files to kebab-case Standardize dashboard filenames from CamelCase to kebab-case for consistency --- .../mysql/{AICostEfficiency.json => ai-cost-efficiency.json} | 0 .../dashboards/mysql/{AIModelROI.json => ai-model-roi.json} | 0 grafana/dashboards/mysql/{ArgoCD.json => argo-cd.json} | 0 .../dashboards/mysql/{AzureDevOps.json => azure-dev-ops.json} | 0 grafana/dashboards/mysql/{BitBucket.json => bit-bucket.json} | 0 grafana/dashboards/mysql/{CircleCI.json => circle-ci.json} | 0 ...evelMetrics.json => component-and-file-level-metrics.json} | 0 ...ContributorExperience.json => contributor-experience.json} | 0 ...on => demo-average-requirement-lead-time-by-assignee.json} | 0 ...mitCountByAuthor.json => demo-commit-count-by-author.json} | 0 .../{DemoDetailedBugInfo.json => demo-detailed-bug-info.json} | 0 .../mysql/{DemoHomepage.json => demo-homepage.json} | 0 ...demo-how-fast-do-we-respond-to-customer-requirements.json} | 0 ...json => demo-is-this-month-more-productive-than-last.json} | 0 ...edOrNot.json => demo-was-our-quality-improved-or-not.json} | 0 ...oductivityHours.json => developer-productivity-hours.json} | 0 .../dashboards/mysql/{DORAByTeam.json => dora-by-team.json} | 0 grafana/dashboards/mysql/{DORADebug.json => dora-debug.json} | 0 ...FailureRate.json => dora-details-change-failure-rate.json} | 0 ...tFrequency.json => dora-details-deployment-frequency.json} | 0 ...json => dora-details-failed-deployment-recovery-time.json} | 0 ...forChanges.json => dora-details-lead-timefor-changes.json} | 0 ...eService.json => dora-details-timeto-restore-service.json} | 0 .../{EngineeringOverview.json => engineering-overview.json} | 0 ...n => engineering-throughput-and-cycle-time-team-view.json} | 0 ...leTime.json => engineering-throughput-and-cycle-time.json} | 0 grafana/dashboards/mysql/{GitHub.json => git-hub.json} | 0 ...ithubCopilotAdoption.json => github-copilot-adoption.json} | 0 ...ACorrelation.json => github-copilot-dora-correlation.json} | 0 ... => github-release-quality-and-contribution-analysis.json} | 4 ++-- .../mysql/{KiroCreditsDORA.json => kiro-credits-dora.json} | 0 .../{LanguageAIHeatmap.json => language-ai-heatmap.json} | 0 .../{MultiAIComparison.json => multi-ai-comparison.json} | 0 grafana/dashboards/mysql/{PagerDuty.json => pager-duty.json} | 0 grafana/dashboards/mysql/{QDevDORA.json => q-dev-dora.json} | 0 .../mysql/{SonarQubeCloud.json => sonar-qube-cloud.json} | 0 ...ingAdoptionTracker.json => steering-adoption-tracker.json} | 0 .../mysql/{WeeklyBugRetro.json => weekly-bug-retro.json} | 0 ...{WeeklyCommunityRetro.json => weekly-community-retro.json} | 0 grafana/dashboards/mysql/{WorkLogs.json => work-logs.json} | 0 40 files changed, 2 insertions(+), 2 deletions(-) rename grafana/dashboards/mysql/{AICostEfficiency.json => ai-cost-efficiency.json} (100%) rename grafana/dashboards/mysql/{AIModelROI.json => ai-model-roi.json} (100%) rename grafana/dashboards/mysql/{ArgoCD.json => argo-cd.json} (100%) rename grafana/dashboards/mysql/{AzureDevOps.json => azure-dev-ops.json} (100%) rename grafana/dashboards/mysql/{BitBucket.json => bit-bucket.json} (100%) rename grafana/dashboards/mysql/{CircleCI.json => circle-ci.json} (100%) rename grafana/dashboards/mysql/{ComponentAndFileLevelMetrics.json => component-and-file-level-metrics.json} (100%) rename grafana/dashboards/mysql/{ContributorExperience.json => contributor-experience.json} (100%) rename grafana/dashboards/mysql/{DemoAverageRequirementLeadTimeByAssignee.json => demo-average-requirement-lead-time-by-assignee.json} (100%) rename grafana/dashboards/mysql/{DemoCommitCountByAuthor.json => demo-commit-count-by-author.json} (100%) rename grafana/dashboards/mysql/{DemoDetailedBugInfo.json => demo-detailed-bug-info.json} (100%) rename grafana/dashboards/mysql/{DemoHomepage.json => demo-homepage.json} (100%) rename grafana/dashboards/mysql/{DemoHowFastDoWeRespondToCustomerRequirements.json => demo-how-fast-do-we-respond-to-customer-requirements.json} (100%) rename grafana/dashboards/mysql/{DemoIsThisMonthMoreProductiveThanLast.json => demo-is-this-month-more-productive-than-last.json} (100%) rename grafana/dashboards/mysql/{DemoWasOurQualityImprovedOrNot.json => demo-was-our-quality-improved-or-not.json} (100%) rename grafana/dashboards/mysql/{DeveloperProductivityHours.json => developer-productivity-hours.json} (100%) rename grafana/dashboards/mysql/{DORAByTeam.json => dora-by-team.json} (100%) rename grafana/dashboards/mysql/{DORADebug.json => dora-debug.json} (100%) rename grafana/dashboards/mysql/{DORADetails-ChangeFailureRate.json => dora-details-change-failure-rate.json} (100%) rename grafana/dashboards/mysql/{DORADetails-DeploymentFrequency.json => dora-details-deployment-frequency.json} (100%) rename grafana/dashboards/mysql/{DORADetails-FailedDeploymentRecoveryTime.json => dora-details-failed-deployment-recovery-time.json} (100%) rename grafana/dashboards/mysql/{DORADetails-LeadTimeforChanges.json => dora-details-lead-timefor-changes.json} (100%) rename grafana/dashboards/mysql/{DORADetails-TimetoRestoreService.json => dora-details-timeto-restore-service.json} (100%) rename grafana/dashboards/mysql/{EngineeringOverview.json => engineering-overview.json} (100%) rename grafana/dashboards/mysql/{EngineeringThroughputAndCycleTimeTeamView.json => engineering-throughput-and-cycle-time-team-view.json} (100%) rename grafana/dashboards/mysql/{EngineeringThroughputAndCycleTime.json => engineering-throughput-and-cycle-time.json} (100%) rename grafana/dashboards/mysql/{GitHub.json => git-hub.json} (100%) rename grafana/dashboards/mysql/{GithubCopilotAdoption.json => github-copilot-adoption.json} (100%) rename grafana/dashboards/mysql/{GithubCopilotDORACorrelation.json => github-copilot-dora-correlation.json} (100%) rename grafana/dashboards/mysql/{GithubReleaseQualityAndContributionAnalysis.json => github-release-quality-and-contribution-analysis.json} (99%) rename grafana/dashboards/mysql/{KiroCreditsDORA.json => kiro-credits-dora.json} (100%) rename grafana/dashboards/mysql/{LanguageAIHeatmap.json => language-ai-heatmap.json} (100%) rename grafana/dashboards/mysql/{MultiAIComparison.json => multi-ai-comparison.json} (100%) rename grafana/dashboards/mysql/{PagerDuty.json => pager-duty.json} (100%) rename grafana/dashboards/mysql/{QDevDORA.json => q-dev-dora.json} (100%) rename grafana/dashboards/mysql/{SonarQubeCloud.json => sonar-qube-cloud.json} (100%) rename grafana/dashboards/mysql/{SteeringAdoptionTracker.json => steering-adoption-tracker.json} (100%) rename grafana/dashboards/mysql/{WeeklyBugRetro.json => weekly-bug-retro.json} (100%) rename grafana/dashboards/mysql/{WeeklyCommunityRetro.json => weekly-community-retro.json} (100%) rename grafana/dashboards/mysql/{WorkLogs.json => work-logs.json} (100%) diff --git a/grafana/dashboards/mysql/AICostEfficiency.json b/grafana/dashboards/mysql/ai-cost-efficiency.json similarity index 100% rename from grafana/dashboards/mysql/AICostEfficiency.json rename to grafana/dashboards/mysql/ai-cost-efficiency.json diff --git a/grafana/dashboards/mysql/AIModelROI.json b/grafana/dashboards/mysql/ai-model-roi.json similarity index 100% rename from grafana/dashboards/mysql/AIModelROI.json rename to grafana/dashboards/mysql/ai-model-roi.json diff --git a/grafana/dashboards/mysql/ArgoCD.json b/grafana/dashboards/mysql/argo-cd.json similarity index 100% rename from grafana/dashboards/mysql/ArgoCD.json rename to grafana/dashboards/mysql/argo-cd.json diff --git a/grafana/dashboards/mysql/AzureDevOps.json b/grafana/dashboards/mysql/azure-dev-ops.json similarity index 100% rename from grafana/dashboards/mysql/AzureDevOps.json rename to grafana/dashboards/mysql/azure-dev-ops.json diff --git a/grafana/dashboards/mysql/BitBucket.json b/grafana/dashboards/mysql/bit-bucket.json similarity index 100% rename from grafana/dashboards/mysql/BitBucket.json rename to grafana/dashboards/mysql/bit-bucket.json diff --git a/grafana/dashboards/mysql/CircleCI.json b/grafana/dashboards/mysql/circle-ci.json similarity index 100% rename from grafana/dashboards/mysql/CircleCI.json rename to grafana/dashboards/mysql/circle-ci.json diff --git a/grafana/dashboards/mysql/ComponentAndFileLevelMetrics.json b/grafana/dashboards/mysql/component-and-file-level-metrics.json similarity index 100% rename from grafana/dashboards/mysql/ComponentAndFileLevelMetrics.json rename to grafana/dashboards/mysql/component-and-file-level-metrics.json diff --git a/grafana/dashboards/mysql/ContributorExperience.json b/grafana/dashboards/mysql/contributor-experience.json similarity index 100% rename from grafana/dashboards/mysql/ContributorExperience.json rename to grafana/dashboards/mysql/contributor-experience.json diff --git a/grafana/dashboards/mysql/DemoAverageRequirementLeadTimeByAssignee.json b/grafana/dashboards/mysql/demo-average-requirement-lead-time-by-assignee.json similarity index 100% rename from grafana/dashboards/mysql/DemoAverageRequirementLeadTimeByAssignee.json rename to grafana/dashboards/mysql/demo-average-requirement-lead-time-by-assignee.json diff --git a/grafana/dashboards/mysql/DemoCommitCountByAuthor.json b/grafana/dashboards/mysql/demo-commit-count-by-author.json similarity index 100% rename from grafana/dashboards/mysql/DemoCommitCountByAuthor.json rename to grafana/dashboards/mysql/demo-commit-count-by-author.json diff --git a/grafana/dashboards/mysql/DemoDetailedBugInfo.json b/grafana/dashboards/mysql/demo-detailed-bug-info.json similarity index 100% rename from grafana/dashboards/mysql/DemoDetailedBugInfo.json rename to grafana/dashboards/mysql/demo-detailed-bug-info.json diff --git a/grafana/dashboards/mysql/DemoHomepage.json b/grafana/dashboards/mysql/demo-homepage.json similarity index 100% rename from grafana/dashboards/mysql/DemoHomepage.json rename to grafana/dashboards/mysql/demo-homepage.json diff --git a/grafana/dashboards/mysql/DemoHowFastDoWeRespondToCustomerRequirements.json b/grafana/dashboards/mysql/demo-how-fast-do-we-respond-to-customer-requirements.json similarity index 100% rename from grafana/dashboards/mysql/DemoHowFastDoWeRespondToCustomerRequirements.json rename to grafana/dashboards/mysql/demo-how-fast-do-we-respond-to-customer-requirements.json diff --git a/grafana/dashboards/mysql/DemoIsThisMonthMoreProductiveThanLast.json b/grafana/dashboards/mysql/demo-is-this-month-more-productive-than-last.json similarity index 100% rename from grafana/dashboards/mysql/DemoIsThisMonthMoreProductiveThanLast.json rename to grafana/dashboards/mysql/demo-is-this-month-more-productive-than-last.json diff --git a/grafana/dashboards/mysql/DemoWasOurQualityImprovedOrNot.json b/grafana/dashboards/mysql/demo-was-our-quality-improved-or-not.json similarity index 100% rename from grafana/dashboards/mysql/DemoWasOurQualityImprovedOrNot.json rename to grafana/dashboards/mysql/demo-was-our-quality-improved-or-not.json diff --git a/grafana/dashboards/mysql/DeveloperProductivityHours.json b/grafana/dashboards/mysql/developer-productivity-hours.json similarity index 100% rename from grafana/dashboards/mysql/DeveloperProductivityHours.json rename to grafana/dashboards/mysql/developer-productivity-hours.json diff --git a/grafana/dashboards/mysql/DORAByTeam.json b/grafana/dashboards/mysql/dora-by-team.json similarity index 100% rename from grafana/dashboards/mysql/DORAByTeam.json rename to grafana/dashboards/mysql/dora-by-team.json diff --git a/grafana/dashboards/mysql/DORADebug.json b/grafana/dashboards/mysql/dora-debug.json similarity index 100% rename from grafana/dashboards/mysql/DORADebug.json rename to grafana/dashboards/mysql/dora-debug.json diff --git a/grafana/dashboards/mysql/DORADetails-ChangeFailureRate.json b/grafana/dashboards/mysql/dora-details-change-failure-rate.json similarity index 100% rename from grafana/dashboards/mysql/DORADetails-ChangeFailureRate.json rename to grafana/dashboards/mysql/dora-details-change-failure-rate.json diff --git a/grafana/dashboards/mysql/DORADetails-DeploymentFrequency.json b/grafana/dashboards/mysql/dora-details-deployment-frequency.json similarity index 100% rename from grafana/dashboards/mysql/DORADetails-DeploymentFrequency.json rename to grafana/dashboards/mysql/dora-details-deployment-frequency.json diff --git a/grafana/dashboards/mysql/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/mysql/dora-details-failed-deployment-recovery-time.json similarity index 100% rename from grafana/dashboards/mysql/DORADetails-FailedDeploymentRecoveryTime.json rename to grafana/dashboards/mysql/dora-details-failed-deployment-recovery-time.json diff --git a/grafana/dashboards/mysql/DORADetails-LeadTimeforChanges.json b/grafana/dashboards/mysql/dora-details-lead-timefor-changes.json similarity index 100% rename from grafana/dashboards/mysql/DORADetails-LeadTimeforChanges.json rename to grafana/dashboards/mysql/dora-details-lead-timefor-changes.json diff --git a/grafana/dashboards/mysql/DORADetails-TimetoRestoreService.json b/grafana/dashboards/mysql/dora-details-timeto-restore-service.json similarity index 100% rename from grafana/dashboards/mysql/DORADetails-TimetoRestoreService.json rename to grafana/dashboards/mysql/dora-details-timeto-restore-service.json diff --git a/grafana/dashboards/mysql/EngineeringOverview.json b/grafana/dashboards/mysql/engineering-overview.json similarity index 100% rename from grafana/dashboards/mysql/EngineeringOverview.json rename to grafana/dashboards/mysql/engineering-overview.json diff --git a/grafana/dashboards/mysql/EngineeringThroughputAndCycleTimeTeamView.json b/grafana/dashboards/mysql/engineering-throughput-and-cycle-time-team-view.json similarity index 100% rename from grafana/dashboards/mysql/EngineeringThroughputAndCycleTimeTeamView.json rename to grafana/dashboards/mysql/engineering-throughput-and-cycle-time-team-view.json diff --git a/grafana/dashboards/mysql/EngineeringThroughputAndCycleTime.json b/grafana/dashboards/mysql/engineering-throughput-and-cycle-time.json similarity index 100% rename from grafana/dashboards/mysql/EngineeringThroughputAndCycleTime.json rename to grafana/dashboards/mysql/engineering-throughput-and-cycle-time.json diff --git a/grafana/dashboards/mysql/GitHub.json b/grafana/dashboards/mysql/git-hub.json similarity index 100% rename from grafana/dashboards/mysql/GitHub.json rename to grafana/dashboards/mysql/git-hub.json diff --git a/grafana/dashboards/mysql/GithubCopilotAdoption.json b/grafana/dashboards/mysql/github-copilot-adoption.json similarity index 100% rename from grafana/dashboards/mysql/GithubCopilotAdoption.json rename to grafana/dashboards/mysql/github-copilot-adoption.json diff --git a/grafana/dashboards/mysql/GithubCopilotDORACorrelation.json b/grafana/dashboards/mysql/github-copilot-dora-correlation.json similarity index 100% rename from grafana/dashboards/mysql/GithubCopilotDORACorrelation.json rename to grafana/dashboards/mysql/github-copilot-dora-correlation.json diff --git a/grafana/dashboards/mysql/GithubReleaseQualityAndContributionAnalysis.json b/grafana/dashboards/mysql/github-release-quality-and-contribution-analysis.json similarity index 99% rename from grafana/dashboards/mysql/GithubReleaseQualityAndContributionAnalysis.json rename to grafana/dashboards/mysql/github-release-quality-and-contribution-analysis.json index f2c354145b5..2bf976806cd 100644 --- a/grafana/dashboards/mysql/GithubReleaseQualityAndContributionAnalysis.json +++ b/grafana/dashboards/mysql/github-release-quality-and-contribution-analysis.json @@ -2706,7 +2706,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitHub_Release_Quality_and_Contribution_Analysis", + "title": "GitHub Release Quality and Contribution Analysis", "uid": "2xuOaQUnk4", "version": 3 -} \ No newline at end of file +} diff --git a/grafana/dashboards/mysql/KiroCreditsDORA.json b/grafana/dashboards/mysql/kiro-credits-dora.json similarity index 100% rename from grafana/dashboards/mysql/KiroCreditsDORA.json rename to grafana/dashboards/mysql/kiro-credits-dora.json diff --git a/grafana/dashboards/mysql/LanguageAIHeatmap.json b/grafana/dashboards/mysql/language-ai-heatmap.json similarity index 100% rename from grafana/dashboards/mysql/LanguageAIHeatmap.json rename to grafana/dashboards/mysql/language-ai-heatmap.json diff --git a/grafana/dashboards/mysql/MultiAIComparison.json b/grafana/dashboards/mysql/multi-ai-comparison.json similarity index 100% rename from grafana/dashboards/mysql/MultiAIComparison.json rename to grafana/dashboards/mysql/multi-ai-comparison.json diff --git a/grafana/dashboards/mysql/PagerDuty.json b/grafana/dashboards/mysql/pager-duty.json similarity index 100% rename from grafana/dashboards/mysql/PagerDuty.json rename to grafana/dashboards/mysql/pager-duty.json diff --git a/grafana/dashboards/mysql/QDevDORA.json b/grafana/dashboards/mysql/q-dev-dora.json similarity index 100% rename from grafana/dashboards/mysql/QDevDORA.json rename to grafana/dashboards/mysql/q-dev-dora.json diff --git a/grafana/dashboards/mysql/SonarQubeCloud.json b/grafana/dashboards/mysql/sonar-qube-cloud.json similarity index 100% rename from grafana/dashboards/mysql/SonarQubeCloud.json rename to grafana/dashboards/mysql/sonar-qube-cloud.json diff --git a/grafana/dashboards/mysql/SteeringAdoptionTracker.json b/grafana/dashboards/mysql/steering-adoption-tracker.json similarity index 100% rename from grafana/dashboards/mysql/SteeringAdoptionTracker.json rename to grafana/dashboards/mysql/steering-adoption-tracker.json diff --git a/grafana/dashboards/mysql/WeeklyBugRetro.json b/grafana/dashboards/mysql/weekly-bug-retro.json similarity index 100% rename from grafana/dashboards/mysql/WeeklyBugRetro.json rename to grafana/dashboards/mysql/weekly-bug-retro.json diff --git a/grafana/dashboards/mysql/WeeklyCommunityRetro.json b/grafana/dashboards/mysql/weekly-community-retro.json similarity index 100% rename from grafana/dashboards/mysql/WeeklyCommunityRetro.json rename to grafana/dashboards/mysql/weekly-community-retro.json diff --git a/grafana/dashboards/mysql/WorkLogs.json b/grafana/dashboards/mysql/work-logs.json similarity index 100% rename from grafana/dashboards/mysql/WorkLogs.json rename to grafana/dashboards/mysql/work-logs.json From dc4fb622f44af9cc2fbae763b9051cf4f87fa90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:55:59 -0300 Subject: [PATCH 06/11] feat(grafana): regenerate postgresql dashboards with improved conversion - Fix _computed suffix duplication in column aliases - Convert MySQL FIELD() to PostgreSQL CASE WHEN - Apply all SQL dialect fixes from updated conversion script - Rename dashboard files to kebab-case for consistency --- grafana/dashboards/postgresql/Asana.json | 28 +- grafana/dashboards/postgresql/Bamboo.json | 22 +- grafana/dashboards/postgresql/DORA.json | 32 +- grafana/dashboards/postgresql/DORAByTeam.json | 1254 ----------------- grafana/dashboards/postgresql/Gitlab.json | 38 +- grafana/dashboards/postgresql/Homepage.json | 2 +- grafana/dashboards/postgresql/Jenkins.json | 22 +- grafana/dashboards/postgresql/Jira.json | 28 +- grafana/dashboards/postgresql/Opsgenie.json | 28 +- grafana/dashboards/postgresql/Sonarqube.json | 30 +- grafana/dashboards/postgresql/TAPD.json | 28 +- grafana/dashboards/postgresql/Taiga.json | 54 +- grafana/dashboards/postgresql/Teambition.json | 28 +- grafana/dashboards/postgresql/Testmo.json | 34 +- grafana/dashboards/postgresql/Zentao.json | 28 +- ...IModelROI.json => ai-cost-efficiency.json} | 16 +- ...ICostEfficiency.json => ai-model-roi.json} | 6 +- .../postgresql/{ArgoCD.json => argo-cd.json} | 12 +- .../{AzureDevOps.json => azure-dev-ops.json} | 38 +- .../{BitBucket.json => bit-bucket.json} | 38 +- .../{CircleCI.json => circle-ci.json} | 22 +- ... => component-and-file-level-metrics.json} | 26 +- ...ience.json => contributor-experience.json} | 22 +- ...ge-requirement-lead-time-by-assignee.json} | 6 +- ....json => demo-commit-count-by-author.json} | 6 +- ...gInfo.json => demo-detailed-bug-info.json} | 4 +- .../{DemoHomepage.json => demo-homepage.json} | 2 +- ...-we-respond-to-customer-requirements.json} | 4 +- ...this-month-more-productive-than-last.json} | 4 +- ...demo-was-our-quality-improved-or-not.json} | 4 +- ...json => developer-productivity-hours.json} | 2 +- .../dashboards/postgresql/dora-by-team.json | 1254 +++++++++++++++++ .../{DORADebug.json => dora-debug.json} | 78 +- ... => dora-details-change-failure-rate.json} | 22 +- ...=> dora-details-deployment-frequency.json} | 30 +- ...ails-failed-deployment-recovery-time.json} | 12 +- ...=> dora-details-lead-timefor-changes.json} | 26 +- ... dora-details-timeto-restore-service.json} | 12 +- ...verview.json => engineering-overview.json} | 54 +- ...-throughput-and-cycle-time-team-view.json} | 54 +- ...ngineering-throughput-and-cycle-time.json} | 34 +- .../postgresql/{GitHub.json => git-hub.json} | 58 +- ...tion.json => github-copilot-adoption.json} | 80 +- ...n => github-copilot-dora-correlation.json} | 78 +- ...se-quality-and-contribution-analysis.json} | 50 +- ...reditsDORA.json => kiro-credits-dora.json} | 16 +- ...IHeatmap.json => language-ai-heatmap.json} | 10 +- ...mparison.json => multi-ai-comparison.json} | 22 +- .../{PagerDuty.json => pager-duty.json} | 24 +- .../{QDevDORA.json => q-dev-dora.json} | 18 +- .../dashboards/postgresql/qdev_executive.json | 16 +- .../postgresql/qdev_feature_metrics.json | 20 +- .../dashboards/postgresql/qdev_logging.json | 12 +- .../dashboards/postgresql/qdev_user_data.json | 18 +- .../postgresql/qdev_user_report.json | 10 +- ...arQubeCloud.json => sonar-qube-cloud.json} | 38 +- ...er.json => steering-adoption-tracker.json} | 12 +- ...klyBugRetro.json => weekly-bug-retro.json} | 44 +- ...Retro.json => weekly-community-retro.json} | 52 +- .../{WorkLogs.json => work-logs.json} | 34 +- 60 files changed, 2028 insertions(+), 2028 deletions(-) delete mode 100644 grafana/dashboards/postgresql/DORAByTeam.json rename grafana/dashboards/postgresql/{AIModelROI.json => ai-cost-efficiency.json} (73%) rename grafana/dashboards/postgresql/{AICostEfficiency.json => ai-model-roi.json} (92%) rename grafana/dashboards/postgresql/{ArgoCD.json => argo-cd.json} (93%) rename grafana/dashboards/postgresql/{AzureDevOps.json => azure-dev-ops.json} (86%) rename grafana/dashboards/postgresql/{BitBucket.json => bit-bucket.json} (86%) rename grafana/dashboards/postgresql/{CircleCI.json => circle-ci.json} (90%) rename grafana/dashboards/postgresql/{ComponentAndFileLevelMetrics.json => component-and-file-level-metrics.json} (89%) rename grafana/dashboards/postgresql/{ContributorExperience.json => contributor-experience.json} (92%) rename grafana/dashboards/postgresql/{DemoAverageRequirementLeadTimeByAssignee.json => demo-average-requirement-lead-time-by-assignee.json} (58%) rename grafana/dashboards/postgresql/{DemoCommitCountByAuthor.json => demo-commit-count-by-author.json} (53%) rename grafana/dashboards/postgresql/{DemoDetailedBugInfo.json => demo-detailed-bug-info.json} (94%) rename grafana/dashboards/postgresql/{DemoHomepage.json => demo-homepage.json} (99%) rename grafana/dashboards/postgresql/{DemoHowFastDoWeRespondToCustomerRequirements.json => demo-how-fast-do-we-respond-to-customer-requirements.json} (95%) rename grafana/dashboards/postgresql/{DemoIsThisMonthMoreProductiveThanLast.json => demo-is-this-month-more-productive-than-last.json} (95%) rename grafana/dashboards/postgresql/{DemoWasOurQualityImprovedOrNot.json => demo-was-our-quality-improved-or-not.json} (91%) rename grafana/dashboards/postgresql/{DeveloperProductivityHours.json => developer-productivity-hours.json} (99%) create mode 100644 grafana/dashboards/postgresql/dora-by-team.json rename grafana/dashboards/postgresql/{DORADebug.json => dora-debug.json} (75%) rename grafana/dashboards/postgresql/{DORADetails-ChangeFailureRate.json => dora-details-change-failure-rate.json} (81%) rename grafana/dashboards/postgresql/{DORADetails-DeploymentFrequency.json => dora-details-deployment-frequency.json} (57%) rename grafana/dashboards/postgresql/{DORADetails-FailedDeploymentRecoveryTime.json => dora-details-failed-deployment-recovery-time.json} (70%) rename grafana/dashboards/postgresql/{DORADetails-LeadTimeforChanges.json => dora-details-lead-timefor-changes.json} (88%) rename grafana/dashboards/postgresql/{DORADetails-TimetoRestoreService.json => dora-details-timeto-restore-service.json} (82%) rename grafana/dashboards/postgresql/{EngineeringOverview.json => engineering-overview.json} (83%) rename grafana/dashboards/postgresql/{EngineeringThroughputAndCycleTimeTeamView.json => engineering-throughput-and-cycle-time-team-view.json} (81%) rename grafana/dashboards/postgresql/{EngineeringThroughputAndCycleTime.json => engineering-throughput-and-cycle-time.json} (86%) rename grafana/dashboards/postgresql/{GitHub.json => git-hub.json} (85%) rename grafana/dashboards/postgresql/{GithubCopilotAdoption.json => github-copilot-adoption.json} (85%) rename grafana/dashboards/postgresql/{GithubCopilotDORACorrelation.json => github-copilot-dora-correlation.json} (59%) rename grafana/dashboards/postgresql/{GithubReleaseQualityAndContributionAnalysis.json => github-release-quality-and-contribution-analysis.json} (70%) rename grafana/dashboards/postgresql/{KiroCreditsDORA.json => kiro-credits-dora.json} (70%) rename grafana/dashboards/postgresql/{LanguageAIHeatmap.json => language-ai-heatmap.json} (89%) rename grafana/dashboards/postgresql/{MultiAIComparison.json => multi-ai-comparison.json} (77%) rename grafana/dashboards/postgresql/{PagerDuty.json => pager-duty.json} (90%) rename grafana/dashboards/postgresql/{QDevDORA.json => q-dev-dora.json} (76%) rename grafana/dashboards/postgresql/{SonarQubeCloud.json => sonar-qube-cloud.json} (87%) rename grafana/dashboards/postgresql/{SteeringAdoptionTracker.json => steering-adoption-tracker.json} (83%) rename grafana/dashboards/postgresql/{WeeklyBugRetro.json => weekly-bug-retro.json} (78%) rename grafana/dashboards/postgresql/{WeeklyCommunityRetro.json => weekly-community-retro.json} (83%) rename grafana/dashboards/postgresql/{WorkLogs.json => work-logs.json} (66%) diff --git a/grafana/dashboards/postgresql/Asana.json b/grafana/dashboards/postgresql/Asana.json index d753420ec2d..9d6346da5c1 100644 --- a/grafana/dashboards/postgresql/Asana.json +++ b/grafana/dashboards/postgresql/Asana.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1142,14 +1142,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'asana%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'asana%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'asana%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'asana%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1163,14 +1163,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1185,7 +1185,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Asana (PostgreSQL)", + "title": "Asana", "uid": "asana-dashboard-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Bamboo.json b/grafana/dashboards/postgresql/Bamboo.json index 674a1037309..64c97e6a0ac 100644 --- a/grafana/dashboards/postgresql/Bamboo.json +++ b/grafana/dashboards/postgresql/Bamboo.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -511,7 +511,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -649,7 +649,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -806,7 +806,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -973,7 +973,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1109,7 +1109,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id = ANY(ARRAY[${plan_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1190,14 +1190,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"bamboo%\" ", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'bamboo%'", "hide": 0, "includeAll": true, "label": "Plan Name", "multi": true, "name": "plan_id", "options": [], - "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"bamboo%\" ", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'bamboo%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1212,7 +1212,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Bamboo (PostgreSQL)", + "title": "Bamboo", "uid": "a90e58d9-7acc-4858-aa77-f606d11a7d4a-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORA.json b/grafana/dashboards/postgresql/DORA.json index 8b042f4b83f..56fd6a8799a 100644 --- a/grafana/dashboards/postgresql/DORA.json +++ b/grafana/dashboards/postgresql/DORA.json @@ -233,7 +233,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", "refId": "A", "select": [ [ @@ -380,7 +380,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_week >= 1 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(high)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(medium)') WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(low)') ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(high)') WHEN median_number_of_deployment_days_per_six_months >= 1 THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(medium)') WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(low)') ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -526,7 +526,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -673,7 +673,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.10 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.20 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'No data' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'No data' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", "refId": "A", "select": [ [ @@ -827,7 +827,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -962,7 +962,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1095,7 +1095,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hours\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hours\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1249,7 +1249,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1407,7 +1407,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1472,14 +1472,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1493,14 +1493,14 @@ "value": "2023" }, "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", + "definition": "SELECT dora_report FROM dora_benchmarks", "hide": 0, "includeAll": false, "label": "DORA Report", "multi": false, "name": "dora_report", "options": [], - "query": "select dora_report from dora_benchmarks", + "query": "SELECT dora_report FROM dora_benchmarks", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1514,14 +1514,14 @@ "value": "Failed Deployment Recovery Time" }, "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "hide": 2, "includeAll": false, "label": "TitleValue", "multi": false, "name": "title_value", "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1537,7 +1537,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "DORA (PostgreSQL)", + "title": "DORA", "uid": "qNo8_0M4z-pg", "version": 8, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORAByTeam.json b/grafana/dashboards/postgresql/DORAByTeam.json deleted file mode 100644 index 8831b86c98f..00000000000 --- a/grafana/dashboards/postgresql/DORAByTeam.json +++ /dev/null @@ -1,1254 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 43, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 16, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "- See [how to config](https://devlake.apache.org/docs/DORA) this dashboard\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, webhook, etc. \n - `Pull Requests` from GitHub PRs, GitLab MRs, BitBucket PRs, Azure DevOps PRs, etc.\n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.\n- You can validate/debug this dashboard with the [DORA validation dashboard](/grafana/d/KGkUnV-Vz/dora-dashboard-validation) \n- You also need to do [team configuration](https://devlake.apache.org/docs/Configuration/TeamConfiguration) to use this dashboard. \n- DORA benchmarks vary in different years. You can switch the benchmarks to change them.\n- In DORA's official report in 2023, metric 'failed deployment recovery time' has replaced 'MTTR'.\n- How does this work? \n - Gets the author of the specific commit and then navigates to the team the user belongs to. \n - Gets the team from the PR's author. \n - Gets the team from the commit author.", - "mode": "markdown" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "refId": "A" - } - ], - "title": "Dashboard Introduction", - "type": "text" - }, - { - "datasource": "postgresql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "blue", - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - }, - "mappings": [], - "noValue": "-", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "low" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "medium" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "high" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "elite" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "type": "color-text" - } - }, - { - "id": "color", - "value": { - "fixedColor": "purple", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 8, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT \"Failed deployment recovery time\" AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT \"Time to restore service\" AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Overall DORA Metrics", - "type": "table" - }, - { - "datasource": "postgresql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "pattern": ".*elite.*", - "result": { - "color": "purple", - "index": 0 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*high.*", - "result": { - "color": "green", - "index": 1 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*medium.*", - "result": { - "color": "yellow", - "index": 2 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*low.*", - "result": { - "color": "red", - "index": 3 - } - }, - "type": "regex" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 16 - }, - "id": 11, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^Deployment Frequency$/", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "group": [], - "metricColumn": "none", - "queryType": "randomWalk", - "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_week >= 1 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(high)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(medium)') WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(low)') ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN CONCAT(median_number_of_deployment_days_per_week, ' deployment days per week(elite)') WHEN median_number_of_deployment_days_per_month >= 1 THEN CONCAT(median_number_of_deployment_days_per_month, ' deployment days per month(high)') WHEN median_number_of_deployment_days_per_six_months >= 1 THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(medium)') WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN CONCAT(median_number_of_deployment_days_per_six_months, ' deployment days per six months(low)') ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", - "refId": "A", - "select": [ - [ - { - "params": [ - "id" - ], - "type": "column" - } - ] - ], - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - }, - "table": "_devlake_tasks", - "timeColumn": "created_at", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Deployment Frequency", - "type": "stat" - }, - { - "datasource": "postgresql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "pattern": ".*elite.*", - "result": { - "color": "purple", - "index": 0 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*high.*", - "result": { - "color": "green", - "index": 1 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*medium.*", - "result": { - "color": "yellow", - "index": 2 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*low.*", - "result": { - "color": "red", - "index": 3 - } - }, - "type": "regex" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 16 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^median_change_lead_time$/", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 30 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_change_lead_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_change_lead_time < 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_change_lead_time >= 180 * 24 * 60 THEN CONCAT(ROUND(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Median Lead Time for Changes", - "type": "stat" - }, - { - "datasource": "postgresql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "pattern": ".*elite.*", - "result": { - "color": "purple", - "index": 0 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*high.*", - "result": { - "color": "green", - "index": 1 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*medium.*", - "result": { - "color": "yellow", - "index": 2 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*low.*", - "result": { - "color": "red", - "index": 3 - } - }, - "type": "regex" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 16 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^change_failure_rate$/", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 4: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.10 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(elite)\") WHEN change_failure_rate <= 0.20 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(high)\") WHEN change_failure_rate <= 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(medium)\") WHEN change_failure_rate > 0.30 THEN CONCAT(ROUND(change_failure_rate * 100, 1), \"%(low)\") ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Change Failure Rate", - "type": "stat" - }, - { - "datasource": "postgresql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "pattern": ".*elite.*", - "result": { - "color": "purple", - "index": 0 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*high.*", - "result": { - "color": "green", - "index": 1 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*medium.*", - "result": { - "color": "yellow", - "index": 2 - } - }, - "type": "regex" - }, - { - "options": { - "pattern": ".*low.*", - "result": { - "color": "red", - "index": 3 - } - }, - "type": "regex" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 16 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^median_time_in_hour$/", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_time_to_resolve < 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "${title_value}", - "type": "stat" - }, - { - "datasource": "postgresql", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 21 - }, - "id": 2, - "options": { - "barRadius": 0, - "barWidth": 0.6, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", - "text": {}, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE $__timeFilter(month_timestamp)", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Monthly deployments", - "type": "barchart" - }, - { - "datasource": "postgresql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "Hours", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 21 - }, - "id": 6, - "options": { - "barRadius": 0, - "barWidth": 0.7, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", - "text": {}, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Median Lead Time for Changes", - "type": "barchart" - }, - { - "datasource": "postgresql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "max": 1, - "min": 0, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "change_failure_rate" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 29 - }, - "id": 5, - "options": { - "barRadius": 0, - "barWidth": 0.6, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", - "text": {}, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* Metric 4: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE $__timeFilter(month_timestamp)", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Change Failure Rate", - "type": "barchart" - }, - { - "datasource": "postgresql", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "Hours", - "axisPlacement": "auto", - "axisSoftMin": 0, - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 29 - }, - "id": 9, - "options": { - "barRadius": 0, - "barWidth": 0.6, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", - "text": {}, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "datasource": "postgresql", - "editorMode": "code", - "format": "table", - "hide": false, - "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name = ANY(ARRAY[${team}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.resolution_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name = ANY(ARRAY[${team}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "${title_value}", - "type": "barchart" - } - ], - "refresh": "", - "schemaVersion": 39, - "tags": [ - "Engineering Leads Dashboard" - ], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": "postgresql", - "definition": "select distinct name from teams", - "hide": 0, - "includeAll": true, - "label": "Team", - "multi": false, - "name": "team", - "options": [], - "query": "select distinct name from teams", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "2023", - "value": "2023" - }, - "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", - "hide": 0, - "includeAll": false, - "label": "DORA Report", - "multi": false, - "name": "dora_report", - "options": [], - "query": "select dora_report from dora_benchmarks", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "Failed Deployment Recovery Time", - "value": "Failed Deployment Recovery Time" - }, - "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", - "hide": 2, - "includeAll": false, - "label": "TitleValue", - "multi": false, - "name": "title_value", - "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6M", - "to": "now" - }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": {}, - "timezone": "utc", - "title": "DORA (by Team) (PostgreSQL)", - "uid": "66YkL8y4z-pg", - "version": 4, - "weekStart": "" -} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/Gitlab.json b/grafana/dashboards/postgresql/Gitlab.json index 0a095be3a80..88412083cb6 100644 --- a/grafana/dashboards/postgresql/Gitlab.json +++ b/grafana/dashboards/postgresql/Gitlab.json @@ -150,7 +150,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -300,7 +300,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -439,7 +439,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -566,7 +566,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -728,7 +728,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"MR: Open\", closed AS \"MR: Closed without merging\", merged AS \"MR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"MR: Open\", closed AS \"MR: Closed without merging\", merged AS \"MR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -832,7 +832,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -969,7 +969,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -1076,7 +1076,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1198,7 +1198,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1312,7 +1312,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1419,7 +1419,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1587,7 +1587,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1774,7 +1774,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1915,7 +1915,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2016,7 +2016,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2171,7 +2171,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2271,14 +2271,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos where id like 'gitlab%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'gitlab%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos where id like 'gitlab%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'gitlab%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2320,7 +2320,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitLab (PostgreSQL)", + "title": "GitLab", "uid": "msSjEq97z-pg", "version": 27, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Homepage.json b/grafana/dashboards/postgresql/Homepage.json index 552f67440ea..c86a2e566bd 100644 --- a/grafana/dashboards/postgresql/Homepage.json +++ b/grafana/dashboards/postgresql/Homepage.json @@ -303,7 +303,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Homepage (PostgreSQL)", + "title": "Homepage", "uid": "lCO8w-pVk-pg", "version": 4, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Jenkins.json b/grafana/dashboards/postgresql/Jenkins.json index a6e44d916a2..9fce9549e64 100644 --- a/grafana/dashboards/postgresql/Jenkins.json +++ b/grafana/dashboards/postgresql/Jenkins.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -510,7 +510,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -631,7 +631,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -788,7 +788,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -955,7 +955,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1091,7 +1091,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id = ANY(ARRAY[${job_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1173,14 +1173,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"jenkins%\" ", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'jenkins%'", "hide": 0, "includeAll": true, "label": "Job Name", "multi": true, "name": "job_id", "options": [], - "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"jenkins%\" ", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'jenkins%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1195,7 +1195,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Jenkins (PostgreSQL)", + "title": "Jenkins", "uid": "W8AiDFQnk-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Jira.json b/grafana/dashboards/postgresql/Jira.json index 9d2aad195d2..d95e8e6cf06 100644 --- a/grafana/dashboards/postgresql/Jira.json +++ b/grafana/dashboards/postgresql/Jira.json @@ -177,7 +177,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -282,7 +282,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -420,7 +420,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -511,7 +511,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -630,7 +630,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -744,7 +744,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -849,7 +849,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -972,7 +972,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1090,7 +1090,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1197,14 +1197,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'jira%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'jira%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'jira%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'jira%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1218,14 +1218,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1241,7 +1241,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "Jira (PostgreSQL)", + "title": "Jira", "uid": "F5vqBQl7z-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Opsgenie.json b/grafana/dashboards/postgresql/Opsgenie.json index 7dd6948eebb..1d12c5ae203 100644 --- a/grafana/dashboards/postgresql/Opsgenie.json +++ b/grafana/dashboards/postgresql/Opsgenie.json @@ -177,7 +177,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -280,7 +280,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -412,7 +412,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.title AS \"Title\", i.issue_key AS \"Id\", STRING_AGG(b.name, \", \") AS \"Service(s)\", i.description AS \"Description\", i.original_status AS \"Original Status\", i.priority AS \"Priority\", i.created_date AS \"Created Date\", i.updated_date AS \"Updated Date\", ROUND((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)), 1) AS \"Lead Time Days\", i.url AS \"URL\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY i.title, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, i.lead_time_minutes, i.url", + "rawSql": "SELECT i.title AS \"Title\", i.issue_key AS \"Id\", STRING_AGG(b.name, ', ') AS \"Service(s)\", i.description AS \"Description\", i.original_status AS \"Original Status\", i.priority AS \"Priority\", i.created_date AS \"Created Date\", i.updated_date AS \"Updated Date\", ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Lead Time Days\", i.url AS \"URL\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY i.title, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, i.lead_time_minutes, i.url", "refId": "A", "select": [ [ @@ -515,7 +515,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'closed' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'closed' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -622,7 +622,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -756,7 +756,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -890,7 +890,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -999,7 +999,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -1138,7 +1138,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1240,7 +1240,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1465,7 +1465,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT i.title AS \"Incident Name\", CONCAT('[', STRING_AGG(CONCAT('{'name': '', a.assignee_name, ''', ', 'type': '', r.type, ''}'), \", \"), ']') AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (SUBSTRING_INDEX(i.id, \":\", -1) = SUBSTRING_INDEX(a.issue_id, \":\", -1)) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", + "rawSql": "SELECT i.title AS \"Incident Name\", '[' || STRING_AGG('{\"name\": \"' || a.assignee_name || '\"' || ', \"type\": \"' || r.type || '\"}', ', ') || ']' AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (REVERSE(SPLIT_PART(REVERSE(i.id), ':', 1)) = REVERSE(SPLIT_PART(REVERSE(a.issue_id), ':', 1))) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", "refId": "A", "sql": { "columns": [ @@ -1507,14 +1507,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'opsgenie%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'opsgenie%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'opsgenie%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'opsgenie%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1529,7 +1529,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Opsgenie (PostgreSQL)", + "title": "Opsgenie", "uid": "b4556439-f173-4411-93d4-65f261726d24-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Sonarqube.json b/grafana/dashboards/postgresql/Sonarqube.json index 478ef6ff6a0..91d05aa1f8f 100644 --- a/grafana/dashboards/postgresql/Sonarqube.json +++ b/grafana/dashboards/postgresql/Sonarqube.json @@ -151,7 +151,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Bugs\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'BUG' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Bugs\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'BUG' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -239,7 +239,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Vulnerabilities\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'VULNERABILITY' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Vulnerabilities\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'VULNERABILITY' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -328,7 +328,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -416,7 +416,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS \"Reviewed\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -530,7 +530,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1), '% ', 'Coverage on ', ROUND(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines to cover') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -619,7 +619,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS \"Debt\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -708,7 +708,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -822,7 +822,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -910,7 +910,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1), '% ', 'Duplications on ', ROUND(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1000,7 +1000,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", CONCAT(ROUND(coverage, 2), '%') AS \"Coverage\", CONCAT(ROUND(duplicated_lines_density, 2), '%') AS \"Duplications\" FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ @@ -1044,14 +1044,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from cq_projects", + "definition": "SELECT name || '--' || id AS text FROM cq_projects", "hide": 0, "includeAll": true, "label": "SonarQube Project", "multi": true, "name": "project_id", "options": [], - "query": "select concat(name, '--', id) as text from cq_projects", + "query": "SELECT name || '--' || id AS text FROM cq_projects", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1069,14 +1069,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct severity from cq_issues where id like 'sonar%'", + "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", "hide": 0, "includeAll": true, "label": "Severity", "multi": true, "name": "severity", "options": [], - "query": "select distinct severity from cq_issues where id like 'sonar%'", + "query": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1091,7 +1091,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "SonarQube Server (PostgreSQL)", + "title": "SonarQube Server", "uid": "WA0qbuJ4k-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/TAPD.json b/grafana/dashboards/postgresql/TAPD.json index 697c5bb4354..eddd05e9888 100644 --- a/grafana/dashboards/postgresql/TAPD.json +++ b/grafana/dashboards/postgresql/TAPD.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'tapd%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'tapd%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'tapd%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'tapd%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1166,14 +1166,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1188,7 +1188,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "TAPD (PostgreSQL)", + "title": "TAPD", "uid": "hi-907hVk-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Taiga.json b/grafana/dashboards/postgresql/Taiga.json index 393503b7079..bdd2e66b3c7 100644 --- a/grafana/dashboards/postgresql/Taiga.json +++ b/grafana/dashboards/postgresql/Taiga.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Stories\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Closed Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Stories\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Closed Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -813,7 +813,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -928,7 +928,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1042,7 +1042,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1173,7 +1173,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1258,7 +1258,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND i.status = 'DONE'", + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.status = 'DONE'", "refId": "A", "sql": { "columns": [ @@ -1369,7 +1369,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, SUM(ts.total_points) AS \"Story Points\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", SUM(ts.total_points) AS \"Story Points\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1473,7 +1473,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS Assignee, SUM(i.story_point) AS \"Story Points\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", SUM(i.story_point) AS \"Story Points\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1558,7 +1558,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS \"Story Points\", CASE WHEN i.status = 'DONE' THEN 'Completed' ELSE 'In Progress' END AS Status FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY Status", + "rawSql": "SELECT SUM(ts.total_points) AS \"Story Points\", CASE WHEN i.status = 'DONE' THEN 'Completed' ELSE 'In Progress' END AS \"Status\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"Status\"", "refId": "A", "select": [ [ @@ -1666,7 +1666,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS \"To Do\", COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS \"In Progress\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS \"Done\", COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS \"Other\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS \"To Do\", COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS \"In Progress\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS \"Done\", COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS \"Other\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A" } ], @@ -1751,7 +1751,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS Assignee, COUNT(DISTINCT i.id) AS \"User Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", COUNT(DISTINCT i.id) AS \"User Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1854,7 +1854,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, COUNT(DISTINCT ts.user_story_id) AS \"User Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT ts.user_story_id) AS \"User Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1956,7 +1956,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS Milestone, COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Open Stories\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Closed Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = CONCAT('taiga:TaigaUserStory:', ts.connection_id, ':', ts.user_story_id) JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY \"Closed Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Open Stories\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Closed Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Closed Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -2098,7 +2098,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Story\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Task\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issue\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Story\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Task\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issue\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A" } ], @@ -2182,7 +2182,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN i.id ELSE NULL END) AS \"Open\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Closed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN i.id ELSE NULL END) AS \"Open\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Closed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -2272,7 +2272,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Stories\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Tasks\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issues\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epics\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id = ANY(ARRAY[${board_id}]::text[]) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Stories\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Tasks\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issues\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epics\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -2361,7 +2361,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", ROUND(AVG(CAST(COALESCE(i.lead_time_minutes, (EXTRACT(EPOCH FROM (i.resolution_date - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)), 1) AS \"Avg Lead Time (days)\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.status = 'DONE' AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1 HAVING NOT \"Avg Lead Time (days)\" IS NULL ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", ROUND(CAST(AVG(CAST(COALESCE(i.lead_time_minutes, (EXTRACT(EPOCH FROM (i.resolution_date - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Avg Lead Time (days)\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A" } ], @@ -2388,14 +2388,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'taiga%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'taiga%'", "hide": 0, "includeAll": true, "label": "Choose Project", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'taiga%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'taiga%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2413,14 +2413,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct i.type from issues i join board_issues bi on i.id = bi.issue_id where bi.board_id like 'taiga%'", + "definition": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id LIKE 'taiga%'", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": true, "name": "type", "options": [], - "query": "select distinct i.type from issues i join board_issues bi on i.id = bi.issue_id where bi.board_id like 'taiga%'", + "query": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id LIKE 'taiga%'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2435,7 +2435,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Taiga (PostgreSQL)", + "title": "Taiga", "uid": "taiga-dashboard-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Teambition.json b/grafana/dashboards/postgresql/Teambition.json index ce93e8b1c8d..b4113d7a5f6 100644 --- a/grafana/dashboards/postgresql/Teambition.json +++ b/grafana/dashboards/postgresql/Teambition.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'teambition%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'teambition%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'teambition%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'teambition%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1166,14 +1166,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1188,7 +1188,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Teambition (PostgreSQL)", + "title": "Teambition", "uid": "hi-908hVk-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Testmo.json b/grafana/dashboards/postgresql/Testmo.json index da56d9b5c51..0d7401a8ab3 100644 --- a/grafana/dashboards/postgresql/Testmo.json +++ b/grafana/dashboards/postgresql/Testmo.json @@ -168,7 +168,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(*) AS value FROM (SELECT DISTINCT ar.id, ar.connection_id, ar.testmo_created_at FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT DISTINCT r.id, r.connection_id, r.testmo_created_at FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "rawSql": "SELECT COUNT(*) AS value FROM (SELECT DISTINCT ar.id, ar.connection_id, ar.testmo_created_at FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT DISTINCT r.id, r.connection_id, r.testmo_created_at FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "refId": "A" } ], @@ -237,7 +237,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS value FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "rawSql": "SELECT CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS value FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "refId": "A" } ], @@ -372,7 +372,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, COUNT(CASE WHEN combined.status = 1 THEN 1 ELSE NULL END) AS \"Passed Tests\", COUNT(CASE WHEN combined.status = 2 THEN 1 ELSE NULL END) AS \"Failed Tests\", COUNT(CASE WHEN NOT combined.status IN (1, 2) THEN 1 ELSE NULL END) AS \"Other Tests\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, COUNT(CASE WHEN combined.status = 1 THEN 1 ELSE NULL END) AS \"Passed Tests\", COUNT(CASE WHEN combined.status = 2 THEN 1 ELSE NULL END) AS \"Failed Tests\", COUNT(CASE WHEN NOT combined.status IN (1, 2) THEN 1 ELSE NULL END) AS \"Other Tests\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -431,7 +431,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT r.id) AS value FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT r.id) AS value FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "refId": "A" } ], @@ -500,7 +500,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ar.id) AS value FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT ar.id) AS value FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "refId": "A" } ], @@ -605,7 +605,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(*) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(*) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -689,7 +689,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT p.name AS metric, COUNT(*) AS \"Test Runs\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY p.name ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT p.name AS metric, COUNT(*) AS \"Test Runs\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY p.name ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", "refId": "A" } ], @@ -802,7 +802,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Success Rate\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Success Rate\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -887,7 +887,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Test Count\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Test Count\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1006,7 +1006,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Automation Runs\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Automation Runs\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" }, { @@ -1015,7 +1015,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(DISTINCT r.id) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id = ANY(ARRAY[${connection_id}]::text[]) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(DISTINCT r.id) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "B" } ], @@ -1121,7 +1121,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT combined.id AS \"Test Run ID\", p.name AS \"Project\", combined.name AS \"Test Name\", combined.status_name AS \"Status\", combined.testmo_created_at AS \"Created Date\", combined.source AS \"Source\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id, ar.name, CASE WHEN ar.status = 1 THEN 'Failed' WHEN ar.status = 2 THEN 'Passed' ELSE 'Other' END AS status_name, 'Automation Run' AS source FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id, r.name, r.status_name, 'Test Run' AS source FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id = ANY(ARRAY[${connection_id}]::text[]) ORDER BY combined.testmo_created_at DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT combined.id AS \"Test Run ID\", p.name AS \"Project\", combined.name AS \"Test Name\", combined.status_name AS \"Status\", combined.testmo_created_at AS \"Created Date\", combined.source AS \"Source\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id, ar.name, CASE WHEN ar.status = 1 THEN 'Failed' WHEN ar.status = 2 THEN 'Passed' ELSE 'Other' END AS status_name, 'Automation Run' AS source FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id, r.name, r.status_name, 'Test Run' AS source FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) ORDER BY combined.testmo_created_at DESC NULLS LAST LIMIT 20", "refId": "A" } ], @@ -1182,14 +1182,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from _tool_testmo_connections", + "definition": "SELECT name || '--' || id FROM _tool_testmo_connections", "hide": 0, "includeAll": true, "label": "Choose Connection", "multi": true, "name": "connection_id", "options": [], - "query": "select concat(name, '--', id) from _tool_testmo_connections", + "query": "SELECT name || '--' || id FROM _tool_testmo_connections", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1203,14 +1203,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from _tool_testmo_projects where connection_id in (${connection_id})", + "definition": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from _tool_testmo_projects where connection_id in (${connection_id})", + "query": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1225,7 +1225,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Testmo (PostgreSQL)", + "title": "Testmo", "uid": "testmo-dashboard-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/Zentao.json b/grafana/dashboards/postgresql/Zentao.json index f96beb152cf..18074b4da59 100644 --- a/grafana/dashboards/postgresql/Zentao.json +++ b/grafana/dashboards/postgresql/Zentao.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'zentao%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'zentao%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'zentao%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'zentao%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1166,14 +1166,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1188,7 +1188,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Zentao (PostgreSQL)", + "title": "Zentao", "uid": "yMb4MKh4k-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/AIModelROI.json b/grafana/dashboards/postgresql/ai-cost-efficiency.json similarity index 73% rename from grafana/dashboards/postgresql/AIModelROI.json rename to grafana/dashboards/postgresql/ai-cost-efficiency.json index b8848db01c7..9d463ae3d0f 100644 --- a/grafana/dashboards/postgresql/AIModelROI.json +++ b/grafana/dashboards/postgresql/ai-cost-efficiency.json @@ -138,7 +138,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0) AS DECIMAL), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -189,7 +189,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0) AS DECIMAL), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -240,7 +240,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0) AS DECIMAL), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -316,7 +316,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM merged_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(p.prs, 0), 0), 0), 1) AS \"Credits per PR\" FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _prs AS (SELECT CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS prs FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date) GROUP BY CAST(merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(merged_date AS DATE)) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(p.prs, 0), 0), 1) AS \"Credits per PR\" FROM _credits AS c LEFT JOIN _prs AS p ON c.week_start = p.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -379,7 +379,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(d.deploys, 0), 0), 0), 1) AS \"Credits per Deploy\" FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _deploys AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cicd_deployment_id) AS deploys FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(d.deploys, 0), 0), 1) AS \"Credits per Deploy\" FROM _credits AS c LEFT JOIN _deploys AS d ON c.week_start = d.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -442,7 +442,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day'' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL ''1 day''), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL ''1 day'' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL ''1 day'') SELECT c.week_start AS time, ROUND(CAST(c.credits AS DECIMAL) / NULLIF(NULLIF(NULLIF(i.resolved, 0), 0), 0), 1) AS \"Credits per Issue\" FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", + "rawSql": "WITH _credits AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _issues AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS resolved FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day') SELECT c.week_start AS time, ROUND(CAST(c.credits AS NUMERIC) / NULLIF(NULLIF(i.resolved, 0), 0), 1) AS \"Credits per Issue\" FROM _credits AS c LEFT JOIN _issues AS i ON c.week_start = i.week_start ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -505,7 +505,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL STRING_LITERAL_0_PLACEHOLDER AS time, SUM(credits_used) AS STRING_LITERAL_1_PLACEHOLDER, SUM(total_messages) AS STRING_LITERAL_2_PLACEHOLDER, SUM(chat_conversations) AS STRING_LITERAL_3_PLACEHOLDER FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL STRING_LITERAL_4_PLACEHOLDER ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -531,7 +531,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "AI Cost-Efficiency (PostgreSQL)", + "title": "AI Cost-Efficiency", "uid": "ai_cost_efficiency-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/AICostEfficiency.json b/grafana/dashboards/postgresql/ai-model-roi.json similarity index 92% rename from grafana/dashboards/postgresql/AICostEfficiency.json rename to grafana/dashboards/postgresql/ai-model-roi.json index 49980a1fadb..ffbda08b227 100644 --- a/grafana/dashboards/postgresql/AICostEfficiency.json +++ b/grafana/dashboards/postgresql/ai-model-roi.json @@ -138,7 +138,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT pr.id), 0), 0), 0), 1) AS \"Credits / PR\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM pull_requests WHERE NOT merged_date IS NULL AND $__timeFilter(merged_date)) AS pr WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -189,7 +189,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT cdc.cicd_deployment_id), 0), 0), 0), 1) AS \"Credits / Deploy\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT cicd_deployment_id FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(finished_date)) AS cdc WHERE $__timeFilter(r.date)", "refId": "A" } ], @@ -240,7 +240,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS DECIMAL) / NULLIF(NULLIF(NULLIF(COUNT(DISTINCT i.id), 0), 0), 0), 1) AS \"Credits / Issue\" FROM _tool_q_dev_user_report AS r CROSS JOIN (SELECT DISTINCT id FROM issues WHERE NOT resolution_date IS NULL AND type <> 'INCIDENT' AND $__timeFilter(resolution_date)) AS i WHERE $__timeFilter(r.date)", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/ArgoCD.json b/grafana/dashboards/postgresql/argo-cd.json similarity index 93% rename from grafana/dashboards/postgresql/ArgoCD.json rename to grafana/dashboards/postgresql/argo-cd.json index 53f6936c925..9dec8ebabf0 100644 --- a/grafana/dashboards/postgresql/ArgoCD.json +++ b/grafana/dashboards/postgresql/argo-cd.json @@ -370,7 +370,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS deployment_count FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, deployment_count FROM _deployments ORDER BY time NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS deployment_count FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, deployment_count FROM _deployments ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -438,7 +438,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _deployment_success_rate AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1, 2, 3) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Deployment Success Rate\" FROM _deployment_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _deployment_success_rate AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, result, id FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) GROUP BY 1, 2, 3) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Deployment Success Rate\" FROM _deployment_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -636,7 +636,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _durations AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _durations ORDER BY time NULLS FIRST", + "rawSql": "WITH _durations AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_deployments WHERE $__timeFilter(created_date) AND ('${application_id:raw}' = '' OR '${application_id:raw}' = '$__all' OR cicd_scope_id = ANY(string_to_array('${application_id:raw}', ','))) AND NOT duration_sec IS NULL GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _durations ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1004,14 +1004,14 @@ }, "datasource": "postgresql", "allValue": "$__all", - "definition": "select concat(name, '--', id) as text from cicd_scopes where id like 'argocd:ArgocdApplication:%'", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'argocd:ArgocdApplication:%'", "hide": 0, "includeAll": true, "label": "Application", "multi": true, "name": "application_id", "options": [], - "query": "select concat(name, '--', id) as text from cicd_scopes where id like 'argocd:ArgocdApplication:%'", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'argocd:ArgocdApplication:%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1026,7 +1026,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "ArgoCD (PostgreSQL)", + "title": "ArgoCD", "uid": "Argocd001-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/AzureDevOps.json b/grafana/dashboards/postgresql/azure-dev-ops.json similarity index 86% rename from grafana/dashboards/postgresql/AzureDevOps.json rename to grafana/dashboards/postgresql/azure-dev-ops.json index ef633ebcfe0..5b0f3746cf8 100644 --- a/grafana/dashboards/postgresql/AzureDevOps.json +++ b/grafana/dashboards/postgresql/azure-dev-ops.json @@ -151,7 +151,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -301,7 +301,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -440,7 +440,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -572,7 +572,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -1083,7 +1083,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1205,7 +1205,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1319,7 +1319,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1426,7 +1426,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1594,7 +1594,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1781,7 +1781,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2023,7 +2023,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2278,14 +2278,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos where id like 'azure%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'azure%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos where id like 'azure%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'azure%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2328,7 +2328,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Azure DevOps (PostgreSQL)", + "title": "Azure DevOps", "uid": "ba7e3a95-80ed-4067-a54b-2a82758eb3dd-pg", "version": 5, "weekStart": "" diff --git a/grafana/dashboards/postgresql/BitBucket.json b/grafana/dashboards/postgresql/bit-bucket.json similarity index 86% rename from grafana/dashboards/postgresql/BitBucket.json rename to grafana/dashboards/postgresql/bit-bucket.json index 0ed1342cee8..1e658b09f07 100644 --- a/grafana/dashboards/postgresql/BitBucket.json +++ b/grafana/dashboards/postgresql/bit-bucket.json @@ -154,7 +154,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -304,7 +304,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -443,7 +443,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -575,7 +575,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -1083,7 +1083,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1205,7 +1205,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1319,7 +1319,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1426,7 +1426,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1594,7 +1594,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_count, failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1781,7 +1781,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2023,7 +2023,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2278,14 +2278,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos where id like 'bitbucket%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'bitbucket%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos where id like 'bitbucket%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'bitbucket%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2328,7 +2328,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Bitbucket (PostgreSQL)", + "title": "Bitbucket", "uid": "4LzQHZa4k-pg", "version": 5, "weekStart": "" diff --git a/grafana/dashboards/postgresql/CircleCI.json b/grafana/dashboards/postgresql/circle-ci.json similarity index 90% rename from grafana/dashboards/postgresql/CircleCI.json rename to grafana/dashboards/postgresql/circle-ci.json index 36ad98bdafd..39c0ce38636 100644 --- a/grafana/dashboards/postgresql/CircleCI.json +++ b/grafana/dashboards/postgresql/circle-ci.json @@ -122,7 +122,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", "refId": "A", "select": [ [ @@ -227,7 +227,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", "refId": "A", "select": [ [ @@ -415,7 +415,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[])", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", "refId": "A", "select": [ [ @@ -658,7 +658,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -817,7 +817,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY time, result, id) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -988,7 +988,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -1144,7 +1144,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id = ANY(ARRAY[${full_name}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1242,14 +1242,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from cicd_scopes where id like \"%circleci%\" ", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE '%circleci%'", "hide": 0, "includeAll": true, "label": "CircleCI Project", "multi": true, "name": "full_name", "options": [], - "query": "select concat(name, '--', id) as text from cicd_scopes where id like \"%circleci%\" ", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE '%circleci%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1265,7 +1265,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "CircleCI (PostgreSQL)", + "title": "CircleCI", "uid": "CircleCI-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json b/grafana/dashboards/postgresql/component-and-file-level-metrics.json similarity index 89% rename from grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json rename to grafana/dashboards/postgresql/component-and-file-level-metrics.json index f5fc5c019ca..7b951305044 100644 --- a/grafana/dashboards/postgresql/ComponentAndFileLevelMetrics.json +++ b/grafana/dashboards/postgresql/component-and-file-level-metrics.json @@ -137,7 +137,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY author_name, author_id ORDER BY commit_nums DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT author_name, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY commit_nums DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -253,7 +253,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT author_name, SUM(additions - deletions) AS cnt FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY author_name, author_id ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT author_name, SUM(additions - deletions) AS cnt FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -395,7 +395,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS weekday, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE $__timeFilter(commits.authored_date) AND commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY weekday", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE $__timeFilter(commits.authored_date) AND commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY \"weekday\"", "refId": "A", "select": [ [ @@ -528,7 +528,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS weekday, SUM(additions - deletions) AS changed_nums, SUM(additions) AS total_additions, SUM(deletions) AS total_deletions FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.authored_date) GROUP BY weekday ORDER BY weekday NULLS FIRST", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, SUM(additions - deletions) AS changed_nums, SUM(additions) AS total_additions, SUM(deletions) AS total_deletions FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"weekday\" ORDER BY weekday NULLS FIRST", "refId": "A", "select": [ [ @@ -687,7 +687,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT c.author_name) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT c.author_name) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -789,7 +789,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT author_name) AS author_count, MAX(rst) AS lines_of_code, CAST(MAX(rst) AS NUMERIC) / NULLIF(COUNT(DISTINCT author_name), 0) AS rate FROM commits JOIN (SELECT file_path, commit_files.commit_sha, SUM(additions - deletions) AS rst FROM commit_files JOIN repo_commits AS rc ON commit_files.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path, commit_files.commit_sha) AS a ON a.commit_sha = commits.sha GROUP BY file_path HAVING author_count > 0 ORDER BY rate DESC NULLS LAST", + "rawSql": "SELECT file_path, COUNT(DISTINCT author_name) AS author_count, MAX(rst) AS lines_of_code, CAST(MAX(rst) AS NUMERIC) / NULLIF(COUNT(DISTINCT author_name), 0) AS rate FROM commits JOIN (SELECT file_path, commit_files.commit_sha, SUM(additions - deletions) AS rst FROM commit_files JOIN repo_commits AS rc ON commit_files.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\", commit_files.commit_sha) AS a ON a.commit_sha = commits.sha GROUP BY file_path HAVING author_count > 0 ORDER BY rate DESC NULLS LAST", "refId": "A", "select": [ [ @@ -920,7 +920,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS modified_num FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY file_path ORDER BY modified_num DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS modified_num FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY modified_num DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1054,7 +1054,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(c.authored_date) GROUP BY file_path ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1211,7 +1211,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(commits.committed_date AS DATE)) AS TEXT) WHEN '2' THEN '1.Monday' WHEN '3' THEN '2.Tuesday' WHEN '4' THEN '3.Wednesday' WHEN '5' THEN '4.Thursday' WHEN '6' THEN '5.Friday' WHEN '7' THEN '6.Saturday' WHEN '1' THEN '7.Sunday' END AS wd, COUNT(*) AS lived_lines FROM repo_snapshot JOIN commits ON commits.sha = repo_snapshot.commit_sha WHERE repo_snapshot.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.committed_date) GROUP BY wd ORDER BY wd NULLS FIRST", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(commits.committed_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS wd, COUNT(*) AS lived_lines FROM repo_snapshot JOIN commits ON commits.sha = repo_snapshot.commit_sha WHERE repo_snapshot.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.committed_date) GROUP BY \"wd\" ORDER BY wd NULLS FIRST", "refId": "A", "select": [ [ @@ -1316,7 +1316,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, AVG((EXTRACT(EPOCH FROM (NOW() - commits.committed_date))/86400)) AS line_age FROM repo_snapshot JOIN commits ON repo_snapshot.commit_sha = commits.sha WHERE repo_snapshot.repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(commits.committed_date) GROUP BY file_path ORDER BY line_age DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_path, AVG((EXTRACT(EPOCH FROM (NOW() - commits.committed_date))/86400)) AS line_age FROM repo_snapshot JOIN commits ON repo_snapshot.commit_sha = commits.sha WHERE repo_snapshot.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.committed_date) GROUP BY \"file_path\" ORDER BY line_age DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -1379,14 +1379,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos", + "definition": "SELECT name || '--' || id AS text FROM repos", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos", + "query": "SELECT name || '--' || id AS text FROM repos", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1436,7 +1436,7 @@ ] }, "timezone": "utc", - "title": "Component and File-level Metrics (PostgreSQL)", + "title": "Component and File-level Metrics", "uid": "KxUh7IG4z-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/ContributorExperience.json b/grafana/dashboards/postgresql/contributor-experience.json similarity index 92% rename from grafana/dashboards/postgresql/ContributorExperience.json rename to grafana/dashboards/postgresql/contributor-experience.json index 25b4c94f7ce..0651391fae3 100644 --- a/grafana/dashboards/postgresql/ContributorExperience.json +++ b/grafana/dashboards/postgresql/contributor-experience.json @@ -117,7 +117,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id = ANY(ARRAY[${repo_id}]::text[])) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -201,7 +201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -285,7 +285,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id = ANY(ARRAY[${repo_id}]::text[])) SELECT CAST(100 * SUM(CASE WHEN CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(60, 0) < $iir_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT CAST(100 * SUM(CASE WHEN CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(60, 0) < $iir_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -369,7 +369,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = '$label_gfi' AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = '$label_gfi' AND i.status <> 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -470,7 +470,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH pr_comment_list AS (SELECT pr.id AS issue_id, pr.url, pr.title, pr.created_date AS pr_created_date, prc.id AS comment_id, prc.created_date AS comment_date, prc.account_id, CASE WHEN NOT prc.id IS NULL THEN RANK() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE CAST(pr.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - pr_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM pr_comment_list WHERE comment_rank = 1", + "rawSql": "WITH pr_comment_list AS (SELECT pr.id AS issue_id, pr.url, pr.title, pr.created_date AS pr_created_date, prc.id AS comment_id, prc.created_date AS comment_date, prc.account_id, CASE WHEN NOT prc.id IS NULL THEN RANK() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE CAST(pr.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - pr_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM pr_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -554,7 +554,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -639,7 +639,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * SUM(CASE WHEN CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0) < $prrt_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * SUM(CASE WHEN CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0) < $prrt_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -747,7 +747,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -841,14 +841,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select concat(name, '-', id) from repos", + "definition": "SELECT name || '-' || id FROM repos", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '-', id) from repos", + "query": "SELECT name || '-' || id FROM repos", "refresh": 1, "regex": "/^(?.*)-(?.*)$/", "skipUrlSync": false, @@ -923,7 +923,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Contributor Experience (PostgreSQL)", + "title": "Contributor Experience", "uid": "bwsP5Nz4z-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json b/grafana/dashboards/postgresql/demo-average-requirement-lead-time-by-assignee.json similarity index 58% rename from grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json rename to grafana/dashboards/postgresql/demo-average-requirement-lead-time-by-assignee.json index 4cfe4d08136..9218d5a2780 100644 --- a/grafana/dashboards/postgresql/DemoAverageRequirementLeadTimeByAssignee.json +++ b/grafana/dashboards/postgresql/demo-average-requirement-lead-time-by-assignee.json @@ -111,7 +111,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(resolution_date AS DATE)) + 1) AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", "refId": "A", "select": [ [ @@ -182,7 +182,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(resolution_date AS DATE)) + 1) AS time, assignee_name AS assignee, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i WHERE type = 'REQUIREMENT' AND assignee_id <> '' AND $__timeFilter(resolution_date) GROUP BY 1, 2), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT assignee, lead_time AS this_month_count, time AS this_month FROM _requirements WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT assignee, lead_time AS last_month_count, time AS last_month FROM _requirements WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT assignee, lead_time AS the_month_before_last_count, time AS the_month_before_last FROM _requirements WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.assignee, ''), NULLIF(lmr.assignee, ''), tmblr.assignee) AS \"Assignee\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM the_month_before_last_record AS tmblr LEFT JOIN last_month_record AS lmr ON tmblr.assignee = lmr.assignee LEFT JOIN this_month_record AS tmr ON tmblr.assignee = tmr.assignee ORDER BY 2 NULLS FIRST", "refId": "A", "select": [ [ @@ -244,7 +244,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Average Requirement Lead Time By Assignee (PostgreSQL)", + "title": "Demo-Average Requirement Lead Time By Assignee", "uid": "q27fk7cnk-pg", "version": 6 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json b/grafana/dashboards/postgresql/demo-commit-count-by-author.json similarity index 53% rename from grafana/dashboards/postgresql/DemoCommitCountByAuthor.json rename to grafana/dashboards/postgresql/demo-commit-count-by-author.json index 2b60aa04d6b..bda60d9e20f 100644 --- a/grafana/dashboards/postgresql/DemoCommitCountByAuthor.json +++ b/grafana/dashboards/postgresql/demo-commit-count-by-author.json @@ -112,7 +112,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -187,7 +187,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 day' + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE CONCAT(ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1), '%') END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -251,7 +251,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Commit Count by Author (PostgreSQL)", + "title": "Demo-Commit Count by Author", "uid": "F0iYknc7z-pg", "version": 3 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoDetailedBugInfo.json b/grafana/dashboards/postgresql/demo-detailed-bug-info.json similarity index 94% rename from grafana/dashboards/postgresql/DemoDetailedBugInfo.json rename to grafana/dashboards/postgresql/demo-detailed-bug-info.json index 303e487028c..5607ac3f6c9 100644 --- a/grafana/dashboards/postgresql/DemoDetailedBugInfo.json +++ b/grafana/dashboards/postgresql/demo-detailed-bug-info.json @@ -114,7 +114,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1 ORDER BY 1 DESC NULLS LAST) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count AS \"Bug Count over Month\" FROM bugs ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1 ORDER BY 1 DESC NULLS LAST) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, bug_count AS \"Bug Count over Month\" FROM bugs ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -309,7 +309,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Detailed Bug Info (PostgreSQL)", + "title": "Demo-Detailed Bug Info", "uid": "s48Lzn5nz-pg", "version": 8 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoHomepage.json b/grafana/dashboards/postgresql/demo-homepage.json similarity index 99% rename from grafana/dashboards/postgresql/DemoHomepage.json rename to grafana/dashboards/postgresql/demo-homepage.json index 55de971ebf7..84ae6687845 100644 --- a/grafana/dashboards/postgresql/DemoHomepage.json +++ b/grafana/dashboards/postgresql/demo-homepage.json @@ -185,7 +185,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Homepage (PostgreSQL)", + "title": "Demo-Homepage", "uid": "0Rjxknc7z-pg", "version": 2 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json b/grafana/dashboards/postgresql/demo-how-fast-do-we-respond-to-customer-requirements.json similarity index 95% rename from grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json rename to grafana/dashboards/postgresql/demo-how-fast-do-we-respond-to-customer-requirements.json index acfdbc4a06c..82049695c45 100644 --- a/grafana/dashboards/postgresql/DemoHowFastDoWeRespondToCustomerRequirements.json +++ b/grafana/dashboards/postgresql/demo-how-fast-do-we-respond-to-customer-requirements.json @@ -108,7 +108,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 day' AS time, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time_days FROM issues AS i WHERE type = 'REQUIREMENT' AND $__timeFilter(resolution_date) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, lead_time_days AS \"Average Requirement Lead Time (day)\" FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(resolution_date AS DATE)) + 1) AS time, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS lead_time_days FROM issues AS i WHERE type = 'REQUIREMENT' AND $__timeFilter(resolution_date) GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, lead_time_days AS \"Average Requirement Lead Time (day)\" FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -217,7 +217,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-How fast do we respond to customer requirements? (PostgreSQL)", + "title": "Demo-How fast do we respond to customer requirements?", "uid": "SupYz7c7z-pg", "version": 4 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json b/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json similarity index 95% rename from grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json rename to grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json index 9da3d2e0876..af34787844e 100644 --- a/grafana/dashboards/postgresql/DemoIsThisMonthMoreProductiveThanLast.json +++ b/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json @@ -138,7 +138,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _commits AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS commit_count FROM commits WHERE NOT message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, commit_count AS \"Commit Count\" FROM _commits ORDER BY time NULLS FIRST", + "rawSql": "WITH _commits AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, COUNT(*) AS commit_count FROM commits WHERE NOT message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, commit_count AS \"Commit Count\" FROM _commits ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -247,7 +247,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Is this month more productive than last? (PostgreSQL)", + "title": "Demo-Is this month more productive than last?", "uid": "ddREk75nk-pg", "version": 3 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json b/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json similarity index 91% rename from grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json rename to grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json index bcc358ea604..76213fceb35 100644 --- a/grafana/dashboards/postgresql/DemoWasOurQualityImprovedOrNot.json +++ b/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json @@ -109,7 +109,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 day' AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> 0) SELECT TO_CHAR(time, '%M %Y') AS month, bug_count_per_1k_loc AS \"Bug Count per 1000 Lines of Code\" FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", + "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> '0') SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, bug_count_per_1k_loc AS \"Bug Count per 1000 Lines of Code\" FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -214,7 +214,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Demo-Was our quality improved or not? (PostgreSQL)", + "title": "Demo-Was our quality improved or not?", "uid": "G4DEk75nz-pg", "version": 4 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DeveloperProductivityHours.json b/grafana/dashboards/postgresql/developer-productivity-hours.json similarity index 99% rename from grafana/dashboards/postgresql/DeveloperProductivityHours.json rename to grafana/dashboards/postgresql/developer-productivity-hours.json index 8a044ddff26..f2cf1217045 100644 --- a/grafana/dashboards/postgresql/DeveloperProductivityHours.json +++ b/grafana/dashboards/postgresql/developer-productivity-hours.json @@ -261,7 +261,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Developer AI Productivity Hours (PostgreSQL)", + "title": "Developer AI Productivity Hours", "uid": "kiro_productivity_hours-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/dora-by-team.json b/grafana/dashboards/postgresql/dora-by-team.json new file mode 100644 index 00000000000..b5d8addbb24 --- /dev/null +++ b/grafana/dashboards/postgresql/dora-by-team.json @@ -0,0 +1,1254 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 43, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 16, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "- See [how to config](https://devlake.apache.org/docs/DORA) this dashboard\n- Data Sources Required: \n - `Deployments` from Jenkins, GitLab CI, GitHub Action, webhook, etc. \n - `Pull Requests` from GitHub PRs, GitLab MRs, BitBucket PRs, Azure DevOps PRs, etc.\n - `Incidents` from Jira issues, GitHub issues, TAPD issues, PagerDuty Incidents, etc. \n- Transformation Required: Define `deployments` and `incidents` in [data transformations](https://devlake.apache.org/docs/Configuration/Tutorial#step-3---add-transformations-optional) while configuring the blueprint of a project.\n- You can validate/debug this dashboard with the [DORA validation dashboard](/grafana/d/KGkUnV-Vz/dora-dashboard-validation) \n- You also need to do [team configuration](https://devlake.apache.org/docs/Configuration/TeamConfiguration) to use this dashboard. \n- DORA benchmarks vary in different years. You can switch the benchmarks to change them.\n- In DORA's official report in 2023, metric 'failed deployment recovery time' has replaced 'MTTR'.\n- How does this work? \n - Gets the author of the specific commit and then navigates to the team the user belongs to. \n - Gets the team from the PR's author. \n - Gets the team from the commit author.", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Dashboard Introduction", + "type": "text" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "noValue": "-", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "low" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "medium" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "high" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "elite" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Overall DORA Metrics", + "type": "table" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Deployment Frequency$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "group": [], + "metricColumn": "none", + "queryType": "randomWalk", + "rawQuery": true, + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "refId": "A", + "select": [ + [ + { + "params": [ + "id" + ], + "type": "column" + } + ] + ], + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + }, + "table": "_devlake_tasks", + "timeColumn": "created_at", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Deployment Frequency", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^median_change_lead_time$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Median Lead Time for Changes", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 16 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^change_failure_rate$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 4: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Change Failure Rate", + "type": "stat" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "pattern": ".*elite.*", + "result": { + "color": "purple", + "index": 0 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*high.*", + "result": { + "color": "green", + "index": 1 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*medium.*", + "result": { + "color": "yellow", + "index": 2 + } + }, + "type": "regex" + }, + { + "options": { + "pattern": ".*low.*", + "result": { + "color": "red", + "index": 3 + } + }, + "type": "regex" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 16 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^median_time_in_hour$/", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "${title_value}", + "type": "stat" + }, + { + "datasource": "postgresql", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 21 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE $__timeFilter(month_timestamp)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Monthly deployments", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 21 + }, + "id": 6, + "options": { + "barRadius": 0, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Median Lead Time for Changes", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "change_failure_rate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 29 + }, + "id": 5, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* Metric 4: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE $__timeFilter(month_timestamp)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Change Failure Rate", + "type": "barchart" + }, + { + "datasource": "postgresql", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hours", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 29 + }, + "id": 9, + "options": { + "barRadius": 0, + "barWidth": 0.6, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "datasource": "postgresql", + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "${title_value}", + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "Engineering Leads Dashboard" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "postgresql", + "definition": "SELECT DISTINCT name FROM teams", + "hide": 0, + "includeAll": true, + "label": "Team", + "multi": false, + "name": "team", + "options": [], + "query": "SELECT DISTINCT name FROM teams", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "2023", + "value": "2023" + }, + "datasource": "postgresql", + "definition": "SELECT dora_report FROM dora_benchmarks", + "hide": 0, + "includeAll": false, + "label": "DORA Report", + "multi": false, + "name": "dora_report", + "options": [], + "query": "SELECT dora_report FROM dora_benchmarks", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Failed Deployment Recovery Time", + "value": "Failed Deployment Recovery Time" + }, + "datasource": "postgresql", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", + "hide": 2, + "includeAll": false, + "label": "TitleValue", + "multi": false, + "name": "title_value", + "options": [], + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6M", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "utc", + "title": "DORA (by Team)", + "uid": "66YkL8y4z-pg", + "version": 4, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/dashboards/postgresql/DORADebug.json b/grafana/dashboards/postgresql/dora-debug.json similarity index 75% rename from grafana/dashboards/postgresql/DORADebug.json rename to grafana/dashboards/postgresql/dora-debug.json index 6f4ecc50741..31088b3013c 100644 --- a/grafana/dashboards/postgresql/DORADebug.json +++ b/grafana/dashboards/postgresql/dora-debug.json @@ -198,7 +198,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE $__timeFilter(cdc.finished_date) GROUP BY pm.project_name, select_status, _raw_data_table, result, environment", + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE $__timeFilter(cdc.finished_date) GROUP BY pm.project_name, \"select_status\", \"_raw_data_table\", \"result\", \"environment\"", "refId": "A", "select": [ [ @@ -320,7 +320,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -445,7 +445,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ @@ -557,7 +557,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -690,7 +690,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)", "refId": "A", "select": [ [ @@ -807,7 +807,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1", "refId": "A", "select": [ [ @@ -924,7 +924,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(day, 'YY/MM') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(CAST(day AS TIMESTAMP), 'YY/MM') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1068,7 +1068,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, pr._raw_data_table, COUNT(*) AS total_number_of_PRs FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND /* \tand pr.merged_date is not null */ /* \tand prm.pr_cycle_time is not null */ $__timeFilter(pr.created_date) GROUP BY 1, 2", + "rawSql": "SELECT pm.project_name, pr._raw_data_table, COUNT(*) AS total_number_of_PRs FROM pull_requests AS pr /* \tjoin project_pr_metrics prm on prm.id = pr.id */ JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND /* \tand pr.merged_date is not null */ /* \tand prm.pr_cycle_time is not null */ $__timeFilter(pr.created_date) GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -1189,7 +1189,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -1357,7 +1357,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, pr.title /* pr.status, */, pr.author_name, pr.url, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pr.id /* pm.project_name in ($project) */ = '$pr_id' AND $__timeFilter(pr.created_date)", + "rawSql": "SELECT pm.project_name, pr.title /* pr.status, */, pr.author_name, pr.url, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pr.id /* pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) */ = '$pr_id' AND $__timeFilter(pr.created_date)", "refId": "A", "select": [ [ @@ -1466,7 +1466,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(cdc.finished_date, 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1574,7 +1574,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS \"No. of merged PRs in table.pull_requests\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS \"No. of merged PRs in table.pull_requests\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL", "refId": "A", "select": [ [ @@ -1604,7 +1604,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"No. of PRs in table.project_pr_metrics\" FROM project_pr_metrics WHERE project_name IN ($project)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"No. of PRs in table.project_pr_metrics\" FROM project_pr_metrics WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", "refId": "B", "select": [ [ @@ -1711,7 +1711,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ @@ -1868,7 +1868,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, pr.title /* pr.status, */, pr.url /* \tpr.author_name, */, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE $__timeFilter(pr.created_date) /* pm.project_name in ($project) */", + "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, pr.title /* pr.status, */, pr.url /* \tpr.author_name, */, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE $__timeFilter(pr.created_date) /* pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) */", "refId": "A", "select": [ [ @@ -1967,7 +1967,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* This query can be used to test if the column \"deployment_commit_id\" is associated with the correct PR */ WITH pr_merge_commits AS (SELECT ppm.id AS pr_id, ppm.deployment_commit_id AS id_1, pr.merge_commit_sha, ppm.project_name FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ppm.project_name IN ($project) AND ppm.id = '$pr_id'), _deployment_commits AS (SELECT DISTINCT cdc1.id AS id_2, cdc1.prev_success_deployment_commit_id, cdc1.commit_sha AS new_commit_sha, cdc2.commit_sha AS old_commit_sha, cdc1.finished_date, cd.commit_sha AS deployed_commits FROM cicd_deployment_commits AS cdc1 LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc1.prev_success_deployment_commit_id = cdc2.id JOIN commits_diffs AS cd ON cdc1.commit_sha = cd.new_commit_sha AND COALESCE(cdc2.commit_sha, '') = cd.old_commit_sha JOIN project_mapping AS pm ON cdc1.cicd_scope_id = pm.row_id WHERE cdc1.result = 'SUCCESS' AND cdc1.environment = 'PRODUCTION' AND pm.project_name IN ($project)), _find_deployment_commit_id_from_pr AS (SELECT pmc.pr_id, pmc.id_1, pmc.merge_commit_sha, pmc.project_name, dc.id_2, dc.prev_success_deployment_commit_id, dc.deployed_commits, RANK() OVER (PARTITION BY pr_id ORDER BY dc.finished_date NULLS FIRST) AS deployment_rank FROM pr_merge_commits AS pmc LEFT JOIN _deployment_commits AS dc ON pmc.merge_commit_sha = dc.deployed_commits) SELECT pr_id, id_1 AS deployment_commit_id /* If \"id_1\" equals \"id_2\", then pass */, CASE WHEN id_1 = COALESCE(id_2, '') THEN 'YES' ELSE 'NO' END AS if_the_mapping_logic_is_correct /* \tid_2, */ FROM _find_deployment_commit_id_from_pr WHERE deployment_rank = 1", + "rawSql": "/* This query can be used to test if the column \"deployment_commit_id\" is associated with the correct PR */ WITH pr_merge_commits AS (SELECT ppm.id AS pr_id, ppm.deployment_commit_id AS id_1, pr.merge_commit_sha, ppm.project_name FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ppm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND ppm.id = '$pr_id'), _deployment_commits AS (SELECT DISTINCT cdc1.id AS id_2, cdc1.prev_success_deployment_commit_id, cdc1.commit_sha AS new_commit_sha, cdc2.commit_sha AS old_commit_sha, cdc1.finished_date, cd.commit_sha AS deployed_commits FROM cicd_deployment_commits AS cdc1 LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc1.prev_success_deployment_commit_id = cdc2.id JOIN commits_diffs AS cd ON cdc1.commit_sha = cd.new_commit_sha AND COALESCE(cdc2.commit_sha, '') = cd.old_commit_sha JOIN project_mapping AS pm ON cdc1.cicd_scope_id = pm.row_id WHERE cdc1.result = 'SUCCESS' AND cdc1.environment = 'PRODUCTION' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)), _find_deployment_commit_id_from_pr AS (SELECT pmc.pr_id, pmc.id_1, pmc.merge_commit_sha, pmc.project_name, dc.id_2, dc.prev_success_deployment_commit_id, dc.deployed_commits, RANK() OVER (PARTITION BY pr_id ORDER BY dc.finished_date NULLS FIRST) AS deployment_rank FROM pr_merge_commits AS pmc LEFT JOIN _deployment_commits AS dc ON pmc.merge_commit_sha = dc.deployed_commits) SELECT pr_id, id_1 AS deployment_commit_id /* If \"id_1\" equals \"id_2\", then pass */, CASE WHEN id_1 = COALESCE(id_2, '') THEN 'YES' ELSE 'NO' END AS if_the_mapping_logic_is_correct /* \tid_2, */ FROM _find_deployment_commit_id_from_pr WHERE deployment_rank = 1", "refId": "A", "select": [ [ @@ -2187,7 +2187,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_pickup_time AS \"PR pickup time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_pickup_time AS \"PR pickup time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", "refId": "B", "select": [ [ @@ -2295,7 +2295,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_review_time AS \"PR review time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_review_time AS \"PR review time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", "refId": "B", "select": [ [ @@ -2373,7 +2373,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR deploy time from cicd_deployment_commits\" FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", + "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR deploy time from cicd_deployment_commits\" FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2403,7 +2403,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_deploy_time AS \"PR deploy time from project_pr_metrics\" FROM project_pr_metrics WHERE id = '$pr_id' AND project_name IN ($project)", + "rawSql": "SELECT id, pr_deploy_time AS \"PR deploy time from project_pr_metrics\" FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", "refId": "B", "select": [ [ @@ -2481,7 +2481,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS \"PR cycle time from lower-level metrics\", ppm.\"pr_cycle_time\" AS \"PR cycle time from project_pr_metrics\" FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name IN ($project) AND pr.id = '$pr_id'", + "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS \"PR cycle time from lower-level metrics\", ppm.\"pr_cycle_time\" AS \"PR cycle time from project_pr_metrics\" FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND pr.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2617,7 +2617,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(cdc.finished_date, 'YY/MM') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", "refId": "A", "select": [ [ @@ -2809,7 +2809,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, i.type, COUNT(1) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id AND pm.\"table\" = 'boards' WHERE pm.project_name IN ($project) AND /* \tand i.type = 'INCIDENT' */ $__timeFilter(i.created_date) GROUP BY pm.project_name, select_status, i._raw_data_table, i.type", + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, i.type, COUNT(1) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id AND pm.\"table\" = 'boards' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND /* \tand i.type = 'INCIDENT' */ $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table, i.type", "refId": "A", "select": [ [ @@ -2930,7 +2930,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(elite)\") WHEN median_recovery_time < 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(high)\") WHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(medium)\") WHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(ROUND(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0), 1), \"(low)\") ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -3045,7 +3045,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name IN ($project) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, COUNT(1) AS issue_count FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name IN ($project) AND $__timeFilter(i.created_date) GROUP BY pm.project_name, select_status, i._raw_data_table", + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, COUNT(1) AS issue_count FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table", "refId": "A", "select": [ [ @@ -3210,7 +3210,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(i.created_date, 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.created_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -3348,7 +3348,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN i.id = NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, _is_collected_data", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN i.id = NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", "refId": "A", "select": [ [ @@ -3464,7 +3464,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project)) SELECT deployment_id AS id, 'DEPLOYMENT' AS type, finished_date AS time FROM _deployments UNION SELECT issue_id AS id, 'INCIDENT' AS type, created_date AS time FROM _incidents ORDER BY time NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)) SELECT deployment_id AS id, 'DEPLOYMENT' AS type, finished_date AS time FROM _deployments UNION SELECT issue_id AS id, 'INCIDENT' AS type, created_date AS time FROM _incidents ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3564,7 +3564,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT cdc.cicd_deployment_id AS deployment_id /* in CFR we use deployment_commit_id as the deployment_id in a specific repo */, cdc.finished_date, pim.id AS incident_id, CASE WHEN NOT pim.id IS NULL THEN 'TRUE' ELSE 'FALSE' END AS has_failure FROM cicd_deployment_commits AS cdc LEFT JOIN project_incident_deployment_relationships AS pim ON cdc.cicd_deployment_id = pim.deployment_id LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND $__timeFilter(cdc.finished_date) ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT cdc.cicd_deployment_id AS deployment_id /* in CFR we use deployment_commit_id as the deployment_id in a specific repo */, cdc.finished_date, pim.id AS incident_id, CASE WHEN NOT pim.id IS NULL THEN 'TRUE' ELSE 'FALSE' END AS has_failure FROM cicd_deployment_commits AS cdc LEFT JOIN project_incident_deployment_relationships AS pim ON cdc.cicd_deployment_id = pim.deployment_id LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(cdc.finished_date) ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -3711,7 +3711,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(deployment_finished_date, 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR($__timeFrom(), '%Y-%m-01') AS DATE) AND CAST(TO_CHAR($__timeTo(), '%Y-%m-01') AS DATE)", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -3770,14 +3770,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -3791,14 +3791,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "select concat(Url, '--', id) from pull_requests", + "definition": "SELECT Url || '--' || id FROM pull_requests", "hide": 0, "includeAll": false, "label": "Pull Request Url", "multi": false, "name": "pr_id", "options": [], - "query": "select concat(Url, '--', id) from pull_requests", + "query": "SELECT Url || '--' || id FROM pull_requests", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -3812,14 +3812,14 @@ "value": "2023" }, "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", + "definition": "SELECT dora_report FROM dora_benchmarks", "hide": 0, "includeAll": false, "label": "DORA Report", "multi": false, "name": "dora_report", "options": [], - "query": "select dora_report from dora_benchmarks", + "query": "SELECT dora_report FROM dora_benchmarks", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -3833,14 +3833,14 @@ "value": "Failed Deployment Recovery Time" }, "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "hide": 2, "includeAll": false, "label": "TitleValue", "multi": false, "name": "title_value", "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -3856,7 +3856,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "DORA Validation (PostgreSQL)", + "title": "DORA Validation", "uid": "KGkUnV-Vz-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json b/grafana/dashboards/postgresql/dora-details-change-failure-rate.json similarity index 81% rename from grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json rename to grafana/dashboards/postgresql/dora-details-change-failure-rate.json index e2a6d5c6489..699fbaa77c8 100644 --- a/grafana/dashboards/postgresql/DORADetails-ChangeFailureRate.json +++ b/grafana/dashboards/postgresql/dora-details-change-failure-rate.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) AS \"change_failure_rate\" FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) AS \"change_failure_rate\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -365,7 +365,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id AS \"deployment_id\", d.deployment_finished_date, pim.id AS incident_id, i.title, i.url, i.url AS \"metric_hidden\", i.created_date /* i.resolution_date, */ FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN issues AS i ON pim.id = i.id WHERE NOT pim.id IS NULL ORDER BY 2 NULLS FIRST) SELECT * FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id AS \"deployment_id\", d.deployment_finished_date, pim.id AS incident_id, i.title, i.url, i.url AS \"metric_hidden\", i.created_date /* i.resolution_date, */ FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN issues AS i ON pim.id = i.id WHERE NOT pim.id IS NULL ORDER BY 2 NULLS FIRST) SELECT * FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT SUM(has_incident) AS \"incident count\", COUNT(deployment_id) AS \"deployment count\" FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT SUM(has_incident) AS \"incident count\", COUNT(deployment_id) AS \"deployment count\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -627,7 +627,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name IN ($project) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project) AND $__timeFilter(i.created_date)) SELECT finished_date AS \"Time (Ascending)\", deployment_id AS \"Entity ID\", 'DEPLOYMENT' AS \"Entity Type (Deployment/Incident)\" FROM _deployments UNION SELECT created_date AS \"Time (Ascending)\", issue_id AS \"Entity ID\", 'INCIDENT' AS \"Entity Type (Deployment/Incident)\" FROM _incidents ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT finished_date AS \"Time (Ascending)\", deployment_id AS \"Entity ID\", 'DEPLOYMENT' AS \"Entity Type (Deployment/Incident)\" FROM _deployments UNION SELECT created_date AS \"Time (Ascending)\", issue_id AS \"Entity ID\", 'INCIDENT' AS \"Entity Type (Deployment/Incident)\" FROM _incidents ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -687,14 +687,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -708,14 +708,14 @@ "value": "2023" }, "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", + "definition": "SELECT dora_report FROM dora_benchmarks", "hide": 0, "includeAll": false, "label": "DORA Report", "multi": false, "name": "dora_report", "options": [], - "query": "select dora_report from dora_benchmarks", + "query": "SELECT dora_report FROM dora_benchmarks", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -729,14 +729,14 @@ "value": "Failed Deployment Recovery Time" }, "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "hide": 2, "includeAll": false, "label": "TitleValue", "multi": false, "name": "title_value", "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -751,7 +751,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "DORA Details - Change Failure Rate (PostgreSQL)", + "title": "DORA Details - Change Failure Rate", "uid": "Change-failure-rate-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json b/grafana/dashboards/postgresql/dora-details-deployment-frequency.json similarity index 57% rename from grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json rename to grafana/dashboards/postgresql/dora-details-deployment-frequency.json index 4b5b50c6af3..93ec2ba7998 100644 --- a/grafana/dashboards/postgresql/DORADetails-DeploymentFrequency.json +++ b/grafana/dashboards/postgresql/dora-details-deployment-frequency.json @@ -257,7 +257,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.display_title, cdc.url, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, CASE WHEN display_title = '' THEN 'N/A' ELSE display_title END AS display_title, url, url AS metric_hidden, result /* a deployment may have multiple deployment_commits */ /* id as deployment_commit_id, */, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date) ORDER BY finished_date DESC NULLS LAST", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.display_title, cdc.url, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, CASE WHEN display_title = '' THEN 'N/A' ELSE display_title END AS display_title, url, url AS metric_hidden, result /* a deployment may have multiple deployment_commits */ /* id as deployment_commit_id, */, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date) ORDER BY finished_date DESC NULLS LAST", "refId": "A", "select": [ [ @@ -375,7 +375,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_days AS (/* Construct the last few calendar days within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT TO_CHAR(last_few_calendar_days.day, '%Y-%m-%d') AS day, COALESCE(COUNT(d.finished_date), 0) AS successful_deployments_to_prod FROM last_few_calendar_days LEFT JOIN _deployments AS d ON last_few_calendar_days.day = CAST(d.finished_date AS DATE) GROUP BY 1 ORDER BY 1 DESC NULLS LAST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_days AS (/* Construct the last few calendar days within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT TO_CHAR(CAST(last_few_calendar_days.day AS TIMESTAMP), 'YYYY-MM-DD') AS day, COALESCE(COUNT(d.finished_date), 0) AS successful_deployments_to_prod FROM last_few_calendar_days LEFT JOIN _deployments AS d ON last_few_calendar_days.day = CAST(d.finished_date AS DATE) GROUP BY 1 ORDER BY 1 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -503,7 +503,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), calendar_weeks AS (SELECT DISTINCT CAST(CAST(day AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM last_few_calendar_months ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '6 DAY', '%m/%d')) AS week, days_deployed FROM calendar_weeks AS cw LEFT JOIN _days_weekly_deploy AS b ON cw.start_of_week = b.week", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), calendar_weeks AS (SELECT DISTINCT CAST(CAST(day AS DATE) + -(EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM last_few_calendar_months ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') AS week, days_deployed FROM calendar_weeks AS cw LEFT JOIN _days_weekly_deploy AS b ON cw.start_of_week = b.week", "refId": "A", "select": [ [ @@ -633,7 +633,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(month, 'YY/MM') AS month, days_deployed FROM _days_monthly_deploy", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month, days_deployed FROM _days_monthly_deploy", "refId": "A", "select": [ [ @@ -762,7 +762,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT CONCAT(TO_CHAR(month - INTERVAL '5 MONTH', 'YY/MM'), ' ~ ', TO_CHAR(month, 'YY/MM')) AS month_range, days_deployed_per_six_months AS days_deployed FROM _days_six_months_deploy ORDER BY month NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month - INTERVAL '5 MONTH' AS TIMESTAMP), 'YY/MM') || ' ~ ' || TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month_range, days_deployed_per_six_months AS days_deployed FROM _days_six_months_deploy ORDER BY month NULLS FIRST", "refId": "A", "select": [ [ @@ -925,7 +925,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_week AS \"Median weekly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\", */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_week AS \"Median weekly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\", */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1063,7 +1063,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_month AS \"Median monthly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_month AS \"Median monthly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1201,7 +1201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 day' AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM month) * 12 + EXTRACT(MONTH FROM month)) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_six_months AS \"Median semi-annual deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_six_months AS \"Median semi-annual deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1263,14 +1263,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1284,14 +1284,14 @@ "value": "2023" }, "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", + "definition": "SELECT dora_report FROM dora_benchmarks", "hide": 0, "includeAll": false, "label": "DORA Report", "multi": false, "name": "dora_report", "options": [], - "query": "select dora_report from dora_benchmarks", + "query": "SELECT dora_report FROM dora_benchmarks", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1305,14 +1305,14 @@ "value": "Failed Deployment Recovery Time" }, "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "hide": 2, "includeAll": false, "label": "TitleValue", "multi": false, "name": "title_value", "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1328,7 +1328,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "DORA Details - Deployment Frequency (PostgreSQL)", + "title": "DORA Details - Deployment Frequency", "uid": "Deployment-frequency-pg", "version": 8, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json similarity index 70% rename from grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json rename to grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json index 44be8b405a1..441c5d5dabb 100644 --- a/grafana/dashboards/postgresql/DORADetails-FailedDeploymentRecoveryTime.json +++ b/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json @@ -121,7 +121,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 AND EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No deployments and incidents' WHEN EXISTS(SELECT COUNT(d.deployment_id) FROM _deployments) = 0 THEN 'No Deployments' WHEN EXISTS(SELECT COUNT(i.incident_id) FROM incidents) = 0 THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN NOT median_recovery_time IS NULL THEN CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) ELSE 'No data' END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN NOT median_recovery_time IS NULL THEN (CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0))::TEXT ELSE 'No data' END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", "refId": "D", "sql": { "columns": [ @@ -314,7 +314,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT fd.deployment_id AS \"deployment_id\", fd.deployment_finished_date, i.id AS incident_caused_by_deployment /* date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month, */, i.title AS incident_title, i.url AS incident_url, i.url AS \"metric_hidden\", i.resolution_date AS incident_resolution_date /* i.created_date as incident_create_date, */, (EXTRACT(EPOCH FROM (i.resolution_date - fd.deployment_finished_date))/3600) AS \"failed_deployment_recovery_time\" FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)) SELECT * FROM _incidents_for_deployments WHERE NOT incident_resolution_date IS NULL", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT fd.deployment_id AS \"deployment_id\", fd.deployment_finished_date, i.id AS incident_caused_by_deployment /* date_format(fd.deployment_finished_date, '%y/%m') as deployment_finished_month, */, i.title AS incident_title, i.url AS incident_url, i.url AS \"metric_hidden\", i.resolution_date AS incident_resolution_date /* i.created_date as incident_create_date, */, (EXTRACT(EPOCH FROM (i.resolution_date - fd.deployment_finished_date))/3600) AS \"failed_deployment_recovery_time\" FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)) SELECT * FROM _incidents_for_deployments WHERE NOT incident_resolution_date IS NULL", "refId": "A", "select": [ [ @@ -409,7 +409,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(fd.deployment_finished_date, 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", "refId": "D", "sql": { "columns": [ @@ -448,14 +448,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -471,7 +471,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "DORA Details - Failed Deployment Recovery Time (PostgreSQL)", + "title": "DORA Details - Failed Deployment Recovery Time", "uid": "Failed-deployment-recovery-time-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json b/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json similarity index 88% rename from grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json rename to grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json index 8c8aca0bbe2..546728bd1ac 100644 --- a/grafana/dashboards/postgresql/DORADetails-LeadTimeforChanges.json +++ b/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json @@ -132,7 +132,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -240,7 +240,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS \"Coding Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS \"Coding Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -379,7 +379,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS \"Review Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS \"Review Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -657,7 +657,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -833,7 +833,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, pr.title, pr.url, pr.created_date, ppm.pr_coding_time, ppm.pr_pickup_time, ppm.pr_review_time, ppm.pr_deploy_time, ppm.first_commit_sha, prc.commit_authored_date, cdc.cicd_deployment_id, cdc.name, cdc.finished_date, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id JOIN pull_request_commits AS prc ON prc.commit_sha = ppm.first_commit_sha WHERE pm.project_name IN ($project) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)) SELECT title AS \"PR title\" /* id as \"PR id\", */, url AS \"PR url\", url AS metric_hidden, first_commit_sha AS \"First commit sha\" /* created_date as \"PR created_date\", */, commit_authored_date AS \"First commit authored date\", cicd_deployment_id AS \"Deployment id\", finished_date AS \"Deployment finished_date\" /* name as \"Deployment name\", */, CAST(pr_coding_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_coding_time\", CAST(pr_pickup_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_pickup_time\", CAST(pr_review_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_review_time\", CAST(pr_deploy_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_deploy_time\", CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time FROM _pr_stats", + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, pr.title, pr.url, pr.created_date, ppm.pr_coding_time, ppm.pr_pickup_time, ppm.pr_review_time, ppm.pr_deploy_time, ppm.first_commit_sha, prc.commit_authored_date, cdc.cicd_deployment_id, cdc.name, cdc.finished_date, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id JOIN pull_request_commits AS prc ON prc.commit_sha = ppm.first_commit_sha WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)) SELECT title AS \"PR title\" /* id as \"PR id\", */, url AS \"PR url\", url AS metric_hidden, first_commit_sha AS \"First commit sha\" /* created_date as \"PR created_date\", */, commit_authored_date AS \"First commit authored date\", cicd_deployment_id AS \"Deployment id\", finished_date AS \"Deployment finished_date\" /* name as \"Deployment name\", */, CAST(pr_coding_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_coding_time\", CAST(pr_pickup_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_pickup_time\", CAST(pr_review_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_review_time\", CAST(pr_deploy_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_deploy_time\", CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time FROM _pr_stats", "refId": "A", "select": [ [ @@ -896,14 +896,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -917,14 +917,14 @@ "value": "2023" }, "datasource": "postgresql", - "definition": "select dora_report from dora_benchmarks", + "definition": "SELECT dora_report FROM dora_benchmarks", "hide": 0, "includeAll": false, "label": "DORA Report", "multi": false, "name": "dora_report", "options": [], - "query": "select dora_report from dora_benchmarks", + "query": "SELECT dora_report FROM dora_benchmarks", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -938,14 +938,14 @@ "value": "Failed Deployment Recovery Time" }, "datasource": "postgresql", - "definition": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "definition": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "hide": 2, "includeAll": false, "label": "TitleValue", "multi": false, "name": "title_value", "options": [], - "query": "SELECT \n CASE \n WHEN dora_report = '2023' THEN \"Failed Deployment Recovery Time\"\n WHEN dora_report = '2021' THEN \"Median Time to Restore Service\"\n ELSE NULL \n END AS title_value\nFROM dora_benchmarks\nWHERE dora_report = '${dora_report:raw}'", + "query": "SELECT CASE WHEN dora_report = '2023' THEN 'Failed Deployment Recovery Time' WHEN dora_report = '2021' THEN 'Median Time to Restore Service' ELSE NULL END AS title_value FROM dora_benchmarks WHERE dora_report = '${dora_report:raw}'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -960,7 +960,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "DORA Details - Lead Time for Changes (PostgreSQL)", + "title": "DORA Details - Lead Time for Changes", "uid": "Lead-time-for-changes-pg", "version": 7, "weekStart": "" diff --git a/grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json b/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json similarity index 82% rename from grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json rename to grafana/dashboards/postgresql/dora-details-timeto-restore-service.json index 30688cd3392..153e830203b 100644 --- a/grafana/dashboards/postgresql/DORADetails-TimetoRestoreService.json +++ b/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json @@ -121,7 +121,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5) SELECT CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS median_time_to_resolve_in_hours FROM _median_mttr", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5) SELECT CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS median_time_to_resolve_in_hours FROM _median_mttr", "refId": "D", "sql": { "columns": [ @@ -301,7 +301,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id AS \"incident_id\", i.title, i.url, i.url AS \"metric_hidden\", i.resolution_date /* i.created_date, */, CAST(CAST(lead_time_minutes AS NUMERIC) / NULLIF(60, 0) AS BIGINT) AS time_to_restore_service FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name IN ($project) AND $__timeFilter(i.resolution_date)) SELECT * FROM _incidents ORDER BY resolution_date DESC NULLS LAST", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id AS \"incident_id\", i.title, i.url, i.url AS \"metric_hidden\", i.resolution_date /* i.created_date, */, CAST(CAST(lead_time_minutes AS NUMERIC) / NULLIF(60, 0) AS BIGINT) AS time_to_restore_service FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.resolution_date)) SELECT * FROM _incidents ORDER BY resolution_date DESC NULLS LAST", "refId": "A", "select": [ [ @@ -396,7 +396,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name IN ($project) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date)) SELECT COUNT(id) AS \"incident count\" FROM _incidents", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT COUNT(id) AS \"incident count\" FROM _incidents", "refId": "D", "sql": { "columns": [ @@ -436,14 +436,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -458,7 +458,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "DORA Details - Time to Restore Service (PostgreSQL)", + "title": "DORA Details - Time to Restore Service", "uid": "Time-to-restore-service-pg", "version": 3, "weekStart": "" diff --git a/grafana/dashboards/postgresql/EngineeringOverview.json b/grafana/dashboards/postgresql/engineering-overview.json similarity index 83% rename from grafana/dashboards/postgresql/EngineeringOverview.json rename to grafana/dashboards/postgresql/engineering-overview.json index 3457a601f8a..28bffc3fcb9 100644 --- a/grafana/dashboards/postgresql/EngineeringOverview.json +++ b/grafana/dashboards/postgresql/engineering-overview.json @@ -119,7 +119,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.priority = ANY(ARRAY[${priority}]::text[]) AND i.type = 'BUG' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) AND i.type = 'BUG' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -253,7 +253,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS defect_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.priority = ANY(ARRAY[${priority}]::text[]) AND i.type = 'BUG' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, defect_count FROM _issues ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT i.id) AS defect_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) AND i.type = 'BUG' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, defect_count FROM _issues ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -360,7 +360,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = ANY(ARRAY[${type}]::text[]) AND i.status = 'DONE' AND i.resolution_date BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND i.resolution_date BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -591,7 +591,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT author_name) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT author_name) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)", "refId": "A", "select": [ [ @@ -725,7 +725,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _developers AS (SELECT CAST(c.authored_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT author_name) AS developer_count FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(c.authored_date) AND c.authored_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, developer_count FROM _developers ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _developers AS (SELECT CAST(c.authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(c.authored_date AS DATE)) + 1) AS time, COUNT(DISTINCT author_name) AS developer_count FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(c.authored_date) AND c.authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, developer_count FROM _developers ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -840,7 +840,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'), _total_num_issues AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH') SELECT NOW() AS time, 100 - CAST(100 * (SELECT 1.0 * num_issues_with_sprint_updated FROM _num_issues_with_sprint_updated) AS NUMERIC) / NULLIF((SELECT total_num_issues FROM _total_num_issues), 0) AS ratio", + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'), _total_num_issues AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH') SELECT NOW() AS time, 100 - CAST(100 * (SELECT 1.0 * num_issues_with_sprint_updated FROM _num_issues_with_sprint_updated) AS NUMERIC) / NULLIF((SELECT total_num_issues FROM _total_num_issues), 0) AS ratio", "refId": "A", "select": [ [ @@ -981,7 +981,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time), _total_num_issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT x.time, 100 - 100 * (CAST(1.0 * x.num_issues_with_sprint_updated AS NUMERIC) / NULLIF(y.total_num_issues, 0)) AS delivery_rate FROM _num_issues_with_sprint_updated AS x JOIN _total_num_issues AS y ON x.time = y.time", + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time), _total_num_issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT x.time, 100 - 100 * (CAST(1.0 * x.num_issues_with_sprint_updated AS NUMERIC) / NULLIF(y.total_num_issues, 0)) AS delivery_rate FROM _num_issues_with_sprint_updated AS x JOIN _total_num_issues AS y ON x.time = y.time", "refId": "A", "select": [ [ @@ -1090,7 +1090,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE NOT pr.merged_date IS NULL AND CAST(pr.merged_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE NOT pr.merged_date IS NULL AND CAST(pr.merged_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1231,7 +1231,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT CAST(pr.merged_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS pr_merged_count FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(time, '%M %Y') AS month, pr_merged_count FROM _merged_prs ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _merged_prs AS (SELECT CAST(pr.merged_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.merged_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_merged_count FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_merged_count FROM _merged_prs ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1344,7 +1344,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1483,7 +1483,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time", + "rawSql": "SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\"", "refId": "A", "select": [ [ @@ -1585,7 +1585,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND WEEKDAY(authored_date) BETWEEN 0 AND 4 AND CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH' GROUP BY author_name, CAST(authored_date AS DATE)) SELECT CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) FROM _commits_groupby_name_and_date", + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND (EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4 AND CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' GROUP BY \"author_name\", CAST(authored_date AS DATE)) SELECT CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) FROM _commits_groupby_name_and_date", "refId": "A", "select": [ [ @@ -1717,7 +1717,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND (WEEKDAY(authored_date) BETWEEN 0 AND 4) AND $__timeFilter(authored_date) AND authored_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1, 2) SELECT _day + INTERVAL '1 day' AS time, CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) AS working_days_percentatages_per_month FROM _commits_groupby_name_and_date GROUP BY time", + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND ((EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4) AND $__timeFilter(authored_date) AND authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1, 2) SELECT _day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(_day AS DATE)) + 1) AS time, CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) AS working_days_percentatages_per_month FROM _commits_groupby_name_and_date GROUP BY time", "refId": "A", "select": [ [ @@ -1828,7 +1828,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1969,7 +1969,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS pr_time_to_merge_in_days FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.created_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS pr_time_to_merge_in_days FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.created_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\" ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2075,7 +2075,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS \"Bug\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS \"Strategic\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS \"Non-Strategic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', '%Y-%m-%d') AND TO_DATE('$month', '%Y-%m-%d') + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS \"Bug\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS \"Strategic\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS \"Non-Strategic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2207,7 +2207,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.priority AS \"Priority\", AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS \"Average Age\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY i.priority", + "rawSql": "SELECT i.priority AS \"Priority\", AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS \"Average Age\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY i.priority", "refId": "A", "select": [ [ @@ -2288,14 +2288,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2309,14 +2309,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "select\n distinct(concat(date_format(DATE_ADD(date(created_date), INTERVAL -DAY(date(created_date))+1 DAY), '%Y-%m') , ':', date_format(DATE_ADD(date(created_date), INTERVAL -DAY(date(created_date))+1 DAY), '%Y-%m-%d'))) as month\nfrom\n issues i\norder by month desc", + "definition": "SELECT DISTINCT (TO_CHAR(CAST(CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS TIMESTAMP), 'YYYY-MM') || ':' || TO_CHAR(CAST(CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS TIMESTAMP), 'YYYY-MM-DD')) AS month FROM issues AS i ORDER BY month DESC NULLS LAST", "hide": 0, "includeAll": false, "label": "Selected Month", "multi": false, "name": "month", "options": [], - "query": "select\n distinct(concat(date_format(month_timestamp, '%Y-%m') , ':', date_format(month_timestamp, '%Y-%m-%d'))) as month\nfrom\n calendar_months\nwhere \n month_timestamp <= curdate()\norder by month desc\nlimit 12", + "query": "SELECT DISTINCT (TO_CHAR(CAST(month_timestamp AS TIMESTAMP), 'YYYY-MM') || ':' || TO_CHAR(CAST(month_timestamp AS TIMESTAMP), 'YYYY-MM-DD')) AS month FROM calendar_months WHERE month_timestamp <= CURRENT_DATE ORDER BY month DESC NULLS LAST LIMIT 12", "refresh": 1, "regex": "/^(?.*):(?.*)$/", "skipUrlSync": false, @@ -2334,7 +2334,7 @@ ] }, "datasource": "postgresql", - "definition": "select distinct priority from issues", + "definition": "SELECT DISTINCT priority FROM issues", "description": "Customize what prioriti(es) are considered \"critical\"", "hide": 0, "includeAll": true, @@ -2342,7 +2342,7 @@ "multi": true, "name": "priority", "options": [], - "query": "select distinct priority from issues", + "query": "SELECT DISTINCT priority FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2356,14 +2356,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2379,7 +2379,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "Engineering Overview (PostgreSQL)", + "title": "Engineering Overview", "uid": "ZF6abXX7z-pg", "version": 15, "weekStart": "" diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json similarity index 81% rename from grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json rename to grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json index 6f0a6a6da0a..1ccbb6f0510 100644 --- a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTimeTeamView.json +++ b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json @@ -210,7 +210,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: Total PR Opened\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: Total PR Opened\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: Total PR Opened\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: Total PR Opened\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -380,7 +380,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Opened per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Opened per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Opened per Member\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Opened per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Opened per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Opened per Member\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -533,7 +533,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND merged_date IS NULL THEN NULL END) AS \"Team1: Total PR Merged\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND merged_date IS NULL THEN NULL END) AS \"Team2: Total PR Merged\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS \"Team1: Total PR Merged\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS \"Team2: Total PR Merged\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -686,7 +686,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT merged_date IS NULL THEN id WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN id END) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged per Member\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN id END) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged per Member\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -866,7 +866,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: Issues Completed\" /* count(i.id) AS \"Issues Opened\", */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: Issues Completed\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: Issues Completed\" /* count(i.id) AS \"Issues Opened\", */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: Issues Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1037,7 +1037,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: Issues Completed per Member\", CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: Issues Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: Issues Completed per Member\", CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: Issues Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1234,7 +1234,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS \"Team1: Story Points Completed\" /* count(i.id) AS \"Issues Opened\", */, SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS \"Team2: Story Points Completed\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN story_point ELSE 0 END) AS \"Team1: Story Points Completed\" /* count(i.id) AS \"Issues Opened\", */, SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN story_point ELSE 0 END) AS \"Team2: Story Points Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1405,7 +1405,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team1}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: Story Points Completed per Member\", CAST(SUM(CASE WHEN status = 'DONE' AND team_id = ANY(ARRAY[${team2}]::text[]) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: Story Points Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: Story Points Completed per Member\", CAST(SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: Story Points Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1602,7 +1602,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, prc.id AS comment_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Review Depth\", CAST(COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Review Depth\", CAST(COUNT(DISTINCT comment_id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Review Depth\" FROM _merged_prs GROUP BY 1", + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, prc.id AS comment_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Review Depth\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Review Depth\", CAST(COUNT(DISTINCT comment_id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Review Depth\" FROM _merged_prs GROUP BY 1", "refId": "A", "select": [ [ @@ -1773,7 +1773,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, pr.merge_commit_sha, c.additions + c.deletions AS loc, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: PR Size\", CAST(SUM(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: PR Size\", CAST(SUM(loc) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged Size\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, pr.merge_commit_sha, c.additions + c.deletions AS loc, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND pr.status = 'MERGED' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Size\", CAST(SUM(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Size\", CAST(SUM(loc) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged Size\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1970,7 +1970,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS \"Team1: P0/P1 Bugs\", COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS \"Team2: P0/P1 Bugs\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: P0/P1 Bugs\", COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: P0/P1 Bugs\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2141,7 +2141,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team1}]::text[])), 0) AS \"Team1: P0/P1 Bugs per Member\", CAST(COUNT(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id = ANY(ARRAY[${team2}]::text[])), 0) AS \"Team2: P0/P1 Bugs per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: P0/P1 Bugs per Member\", CAST(COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: P0/P1 Bugs per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2335,7 +2335,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN cycle_time END) AS \"Team1: Avg Cycle Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN cycle_time END) AS \"Team2: Avg Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN cycle_time END) AS \"Team1: Avg Cycle Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN cycle_time END) AS \"Team2: Avg Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2502,7 +2502,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN coding_time END) AS \"Team1: Avg Coding Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN coding_time END) AS \"Team2: Avg Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN coding_time END) AS \"Team1: Avg Coding Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN coding_time END) AS \"Team2: Avg Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2669,7 +2669,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN pickup_time END) AS \"Team1: Avg Pickup Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN pickup_time END) AS \"Team2: Avg Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN pickup_time END) AS \"Team1: Avg Pickup Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN pickup_time END) AS \"Team2: Avg Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2836,7 +2836,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN review_time END) AS \"Team1: Avg Review Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN review_time END) AS \"Team2: Avg Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN review_time END) AS \"Team1: Avg Review Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN review_time END) AS \"Team2: Avg Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -3003,7 +3003,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) THEN deploy_time END) AS \"Team1: Avg Deploy Time(h)\", AVG(CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) THEN deploy_time END) AS \"Team2: Avg Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN deploy_time END) AS \"Team1: Avg Deploy Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN deploy_time END) AS \"Team2: Avg Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -3196,7 +3196,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team1}]::text[]) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team1: PRs Merged w/o Review\", COUNT(DISTINCT CASE WHEN team_id = ANY(ARRAY[${team2}]::text[]) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team2: PRs Merged w/o Review\" FROM _merged_prs GROUP BY 1", + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team1: PRs Merged w/o Review\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team2: PRs Merged w/o Review\" FROM _merged_prs GROUP BY 1", "refId": "A", "select": [ [ @@ -3289,14 +3289,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -3312,14 +3312,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from teams", + "definition": "SELECT name || '--' || id AS text FROM teams", "hide": 0, "includeAll": false, "label": "Team1", "multi": false, "name": "team1", "options": [], - "query": "select concat(name, '--', id) as text from teams", + "query": "SELECT name || '--' || id AS text FROM teams", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -3335,14 +3335,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from teams", + "definition": "SELECT name || '--' || id AS text FROM teams", "hide": 0, "includeAll": false, "label": "Team2", "multi": false, "name": "team2", "options": [], - "query": "select concat(name, '--', id) as text from teams", + "query": "SELECT name || '--' || id AS text FROM teams", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -3361,14 +3361,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct priority from issues where priority != ''", + "definition": "SELECT DISTINCT priority FROM issues WHERE priority <> ''", "hide": 0, "includeAll": true, "label": "P0+P1 Priority", "multi": true, "name": "priority", "options": [], - "query": "select distinct priority from issues where priority != ''", + "query": "SELECT DISTINCT priority FROM issues WHERE priority <> ''", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -3412,7 +3412,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Engineering Throughput and Cycle Time - Team View (PostgreSQL)", + "title": "Engineering Throughput and Cycle Time - Team View", "uid": "nJ1ijje7k-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json similarity index 86% rename from grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json rename to grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json index 263c085e125..4e0ddb8b6e1 100644 --- a/grafana/dashboards/postgresql/EngineeringThroughputAndCycleTime.json +++ b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json @@ -208,7 +208,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS \"PR: Opened\", COUNT(DISTINCT CASE WHEN NOT pr.merged_date IS NULL THEN id ELSE NULL END) AS \"PR: Merged\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT pr.id) AS \"PR: Opened\", COUNT(DISTINCT CASE WHEN NOT pr.merged_date IS NULL THEN id ELSE NULL END) AS \"PR: Merged\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -360,7 +360,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS \"Issues Opened\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Issues Completed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"Issues Opened\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Issues Completed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -483,7 +483,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS \"Story Points Completed\" FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name = ANY(ARRAY[${project}]::text[])) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS \"Story Points Completed\" FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -622,7 +622,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT prc.id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS \"PR Review Depth\" FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL GROUP BY 1", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT prc.id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS \"PR Review Depth\" FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL GROUP BY 1", "refId": "A", "select": [ [ @@ -745,7 +745,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS \"P0/P1 Bugs\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND i.type = 'BUG' AND i.priority = ANY(ARRAY[${priority}]::text[]) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"P0/P1 Bugs\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -868,7 +868,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS \"PR Merged Size\" FROM _pr_commits_data GROUP BY 1", + "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS \"PR Merged Size\" FROM _pr_commits_data GROUP BY 1", "refId": "A", "select": [ [ @@ -1004,7 +1004,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 day' AS time, SUM(CASE WHEN NOT pr.id IN (SELECT pull_request_id FROM pull_request_comments) THEN 1 ELSE 0 END) AS \"PRs Merged w/o Review\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN NOT pr.id IN (SELECT pull_request_id FROM pull_request_comments) THEN 1 ELSE 0 END) AS \"PRs Merged w/o Review\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1149,7 +1149,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1285,7 +1285,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(coding_time) AS \"Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(coding_time) AS \"Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1452,7 +1452,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1619,7 +1619,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(review_time) AS \"Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(review_time) AS \"Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1786,7 +1786,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND pm.project_name = ANY(ARRAY[${project}]::text[]) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 day' AS time, AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1880,14 +1880,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct name from projects", + "definition": "SELECT DISTINCT name FROM projects", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "select distinct name from projects", + "query": "SELECT DISTINCT name FROM projects", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1906,14 +1906,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct priority from issues where priority != ''", + "definition": "SELECT DISTINCT priority FROM issues WHERE priority <> ''", "hide": 0, "includeAll": true, "label": "P0+P1 Priority", "multi": true, "name": "priority", "options": [], - "query": "select distinct priority from issues where priority != ''", + "query": "SELECT DISTINCT priority FROM issues WHERE priority <> ''", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1957,7 +1957,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Engineering Throughput and Cycle Time (PostgreSQL)", + "title": "Engineering Throughput and Cycle Time", "uid": "Jaaimc67k-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/GitHub.json b/grafana/dashboards/postgresql/git-hub.json similarity index 85% rename from grafana/dashboards/postgresql/GitHub.json rename to grafana/dashboards/postgresql/git-hub.json index dbf7222a3eb..b75e629ddfb 100644 --- a/grafana/dashboards/postgresql/GitHub.json +++ b/grafana/dashboards/postgresql/git-hub.json @@ -150,7 +150,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -286,7 +286,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_count AS \"Issue Count\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_count AS \"Issue Count\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -415,7 +415,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -551,7 +551,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS open_issue_count, COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS closed_issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open_issue_count, closed_issue_count FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS open_issue_count, COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS closed_issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"open_issue_count\", closed_issue_count FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -659,7 +659,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE'", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -781,7 +781,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -916,7 +916,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all outstanding bugs */ WITH _outstanding_issues AS (SELECT DISTINCT b.name AS repo_name, i.issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(i.created_date) AND i.status <> 'DONE') SELECT CONCAT('#', issue_key) AS issue_key, title, url, queue_time_in_days FROM _outstanding_issues ORDER BY 4 DESC NULLS LAST LIMIT 20", + "rawSql": "/* Get the queue time of all outstanding bugs */ WITH _outstanding_issues AS (SELECT DISTINCT b.name AS repo_name, i.issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.status <> 'DONE') SELECT '#' || issue_key AS issue_key, title, url, queue_time_in_days FROM _outstanding_issues ORDER BY 4 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -1103,7 +1103,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all outstanding bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(i.created_date) AND i.status <> 'DONE' ORDER BY queue_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the queue time of all outstanding bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.status <> 'DONE' ORDER BY queue_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1216,7 +1216,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1366,7 +1366,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND $__timeFilter(created_date) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1505,7 +1505,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -1638,7 +1638,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1817,7 +1817,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1921,7 +1921,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -2056,7 +2056,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", "refId": "A", "select": [ [ @@ -2164,7 +2164,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -2285,7 +2285,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2391,7 +2391,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND status IN ('CLOSED', 'MERGED')", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND status IN ('CLOSED', 'MERGED')", "refId": "A", "select": [ [ @@ -2512,7 +2512,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND status IN ('CLOSED', 'MERGED') GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, time_to_close AS \"Time to Close\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND status IN ('CLOSED', 'MERGED') GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_close AS \"Time to Close\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2626,7 +2626,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2733,7 +2733,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2901,7 +2901,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, successful_workflow_run_count, failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", + "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_workflow_run_count\", failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3088,7 +3088,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -3245,7 +3245,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY time, result, id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%m/%Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3346,7 +3346,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 day' + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -3501,7 +3501,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 day' AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(time, '%M %Y') ELSE CONCAT('W', TO_CHAR(time, '%u %Y')) END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3601,14 +3601,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos where id like 'github%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'github%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos where id like 'github%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'github%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -3651,7 +3651,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitHub (PostgreSQL)", + "title": "GitHub", "uid": "KXWvOFQnz-pg", "version": 25, "weekStart": "" diff --git a/grafana/dashboards/postgresql/GithubCopilotAdoption.json b/grafana/dashboards/postgresql/github-copilot-adoption.json similarity index 85% rename from grafana/dashboards/postgresql/GithubCopilotAdoption.json rename to grafana/dashboards/postgresql/github-copilot-adoption.json index e9c76efeb20..978d263b859 100644 --- a/grafana/dashboards/postgresql/GithubCopilotAdoption.json +++ b/grafana/dashboards/postgresql/github-copilot-adoption.json @@ -76,7 +76,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT daily_active_users AS \"Daily Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1", + "rawSql": "SELECT daily_active_users AS \"Daily Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1", "refId": "A" } ], @@ -140,7 +140,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -194,7 +194,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(loc_added_sum) AS \"Lines Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT SUM(loc_added_sum) AS \"Lines Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -248,7 +248,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(pr_total_created_by_copilot) AS \"PRs by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT SUM(pr_total_created_by_copilot) AS \"PRs by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -302,7 +302,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Weekly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '7 DAY' AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Weekly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '7 DAY' AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -356,7 +356,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Monthly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '28 DAY' AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Monthly Active Users\" FROM _tool_copilot_user_daily_metrics WHERE day >= CURRENT_DATE - INTERVAL '28 DAY' AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -410,7 +410,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(pr_total_reviewed_by_copilot) AS \"PRs Reviewed by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT SUM(pr_total_reviewed_by_copilot) AS \"PRs Reviewed by Copilot\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -464,7 +464,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(user_initiated_interaction_count) AS \"User-Initiated Interactions\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT SUM(user_initiated_interaction_count) AS \"User-Initiated Interactions\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -549,7 +549,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, daily_active_users AS \"Daily Active\", monthly_active_users AS \"Monthly Active\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, daily_active_users AS \"Daily Active\", monthly_active_users AS \"Monthly Active\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -634,7 +634,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, code_generation_activity_count AS \"Suggestions\", code_acceptance_activity_count AS \"Acceptances\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, code_generation_activity_count AS \"Suggestions\", code_acceptance_activity_count AS \"Acceptances\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -719,7 +719,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, loc_suggested_to_add_sum AS \"LOC Suggested\", loc_added_sum AS \"LOC Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, loc_suggested_to_add_sum AS \"LOC Suggested\", loc_added_sum AS \"LOC Added\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -804,7 +804,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, pr_total_created AS \"PRs Created\", pr_total_created_by_copilot AS \"PRs Created by Copilot\", pr_total_reviewed AS \"PRs Reviewed\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, pr_total_created AS \"PRs Created\", pr_total_created_by_copilot AS \"PRs Created by Copilot\", pr_total_reviewed AS \"PRs Reviewed\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -893,7 +893,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CAST(day - INTERVAL 'WEEKDAY DAY' AS DATE) AS time, CASE WHEN feature IN (SELECT feature FROM (SELECT feature FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY feature HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_features) THEN REPLACE(REPLACE(feature, 'chat_panel_', ''), '_mode', '') ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY time, metric ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS DATE) AS time, CASE WHEN feature IN (SELECT feature FROM (SELECT feature FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"feature\" HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_features) THEN REPLACE(REPLACE(feature, 'chat_panel_', ''), '_mode', '') ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"time\", \"metric\" ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -997,7 +997,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CAST(day - INTERVAL 'WEEKDAY DAY' AS DATE) AS time, CASE WHEN ide IN (SELECT ide FROM (SELECT ide FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY ide HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_ides) THEN ide ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY time, metric ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS DATE) AS time, CASE WHEN ide IN (SELECT ide FROM (SELECT ide FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"ide\" HAVING SUM(user_initiated_interaction_count) > 0 ORDER BY SUM(user_initiated_interaction_count) DESC NULLS LAST LIMIT 4) AS top_ides) THEN ide ELSE 'Other' END AS metric, SUM(user_initiated_interaction_count) AS value FROM _tool_copilot_metrics_by_ide WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"time\", \"metric\" ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1062,7 +1062,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 15", + "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"language\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 15", "refId": "A" } ], @@ -1112,7 +1112,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY model HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"model\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 10", "refId": "A" } ], @@ -1162,7 +1162,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT language AS \"Language\", feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language, feature HAVING SUM(code_generation_activity_count) > 0 ORDER BY language NULLS FIRST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 100", + "rawSql": "SELECT language AS \"Language\", feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"language\", \"feature\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY language NULLS FIRST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 100", "refId": "A" } ], @@ -1216,7 +1216,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Unique Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Unique Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -1270,7 +1270,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Agent Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' AND used_agent = 1", + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Agent Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND used_agent = 1", "refId": "A" } ], @@ -1324,7 +1324,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' AND used_chat = 1", + "rawSql": "SELECT COUNT(DISTINCT user_login) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND used_chat = 1", "refId": "A" } ], @@ -1388,7 +1388,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CAST(COUNT(CASE WHEN NOT last_activity_at IS NULL THEN 1 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(*), 0), 0) AS \"Utilization %\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id}", + "rawSql": "SELECT CAST(COUNT(CASE WHEN NOT last_activity_at IS NULL THEN 1 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(*), 0), 0) AS \"Utilization %\" FROM _tool_copilot_seats WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}')", "refId": "A" } ], @@ -1457,7 +1457,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, CAST(code_acceptance_activity_count AS NUMERIC) / NULLIF(NULLIF(code_generation_activity_count, 0), 0) AS \"Acceptance Rate\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, CAST(code_acceptance_activity_count AS NUMERIC) / NULLIF(NULLIF(code_generation_activity_count, 0), 0) AS \"Acceptance Rate\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -1511,7 +1511,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, ROUND(CAST(loc_added_sum AS NUMERIC) / NULLIF(NULLIF(loc_suggested_to_add_sum, 0), 0), 2) AS \"LOC Yield\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, ROUND(CAST(loc_added_sum AS NUMERIC) / NULLIF(NULLIF(loc_suggested_to_add_sum, 0), 0), 2) AS \"LOC Yield\" FROM _tool_copilot_enterprise_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -1561,7 +1561,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY feature HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT feature AS \"Feature\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"feature\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", "refId": "A" } ], @@ -1611,7 +1611,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY language HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT language AS \"Language\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_language_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"language\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", "refId": "A" } ], @@ -1661,7 +1661,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY model HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT model AS \"Model\", SUM(code_generation_activity_count) AS \"Suggestions\", SUM(code_acceptance_activity_count) AS \"Acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"Acceptance Rate %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"model\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) DESC NULLS LAST, SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 20", "refId": "A" } ], @@ -1724,7 +1724,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT user_login AS \"user_login\", SUM(code_generation_activity_count) AS \"suggestions\", SUM(code_acceptance_activity_count) AS \"acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"acceptance %\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY user_login HAVING SUM(code_generation_activity_count) > 0 ORDER BY SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 25", + "rawSql": "SELECT user_login AS \"user_login\", SUM(code_generation_activity_count) AS \"suggestions\", SUM(code_acceptance_activity_count) AS \"acceptances\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) AS \"acceptance %\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"user_login\" HAVING SUM(code_generation_activity_count) > 0 ORDER BY SUM(code_generation_activity_count) DESC NULLS LAST LIMIT 25", "refId": "A" } ], @@ -1809,7 +1809,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT day AS time, COUNT(DISTINCT CASE WHEN used_agent = 1 THEN user_login END) AS \"Agent Users\", COUNT(DISTINCT CASE WHEN used_chat = 1 THEN user_login END) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT day AS time, COUNT(DISTINCT CASE WHEN used_agent = 1 THEN user_login END) AS \"Agent Users\", COUNT(DISTINCT CASE WHEN used_chat = 1 THEN user_login END) AS \"Chat Users\" FROM _tool_copilot_user_daily_metrics WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY \"day\" ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -1894,7 +1894,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT first_day AS time, COUNT(*) AS \"New Active Users\" FROM (SELECT user_login, MIN(day) AS first_day FROM _tool_copilot_user_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY user_login) AS first_seen WHERE $__timeFilter(first_day) GROUP BY first_day ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT first_day AS time, COUNT(*) AS \"New Active Users\" FROM (SELECT user_login, MIN(day) AS first_day FROM _tool_copilot_user_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY user_login) AS first_seen WHERE $__timeFilter(first_day) GROUP BY first_day ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -1968,7 +1968,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(last_activity_editor, ''), 'unknown') AS \"Editor\", COUNT(*) AS \"Seats\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') GROUP BY COALESCE(NULLIF(last_activity_editor, ''), 'unknown') ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(last_activity_editor, ''), 'unknown') AS \"Editor\", COUNT(*) AS \"Seats\" FROM _tool_copilot_seats WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') GROUP BY COALESCE(NULLIF(last_activity_editor, ''), 'unknown') ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -2018,7 +2018,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(*) AS \"total_seats\", SUM(CASE WHEN NOT last_activity_at IS NULL AND last_activity_at >= UTC_TIMESTAMP() - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"active_seats\", SUM(CASE WHEN last_activity_at IS NULL OR last_activity_at < UTC_TIMESTAMP() - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"inactive_seats\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')", + "rawSql": "SELECT COUNT(*) AS \"total_seats\", SUM(CASE WHEN NOT last_activity_at IS NULL AND last_activity_at >= CURRENT_TIMESTAMP - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"active_seats\", SUM(CASE WHEN last_activity_at IS NULL OR last_activity_at < CURRENT_TIMESTAMP - INTERVAL '30 DAY' THEN 1 ELSE 0 END) AS \"inactive_seats\" FROM _tool_copilot_seats WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')", "refId": "A" } ], @@ -2068,7 +2068,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT user_login AS \"user_login\", last_activity_at AS \"last_activity_at\", plan_type AS \"plan_type\" FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') AND (last_activity_at IS NULL OR last_activity_at < UTC_TIMESTAMP() - INTERVAL '30 DAY') ORDER BY last_activity_at IS NULL DESC NULLS LAST, last_activity_at ASC NULLS FIRST LIMIT 100", + "rawSql": "SELECT user_login AS \"user_login\", last_activity_at AS \"last_activity_at\", plan_type AS \"plan_type\" FROM _tool_copilot_seats WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '') AND (last_activity_at IS NULL OR last_activity_at < CURRENT_TIMESTAMP - INTERVAL '30 DAY') ORDER BY last_activity_at IS NULL DESC NULLS LAST, last_activity_at ASC NULLS FIRST LIMIT 100", "refId": "A" } ], @@ -2135,7 +2135,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE((EXTRACT(EPOCH FROM (UTC_DATE() - MAX(day)))/86400), 9999) AS \"Days Since Latest Data\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT COALESCE((EXTRACT(EPOCH FROM (CURRENT_DATE AT TIME ZONE 'UTC' - MAX(day)))/86400), 9999) AS \"Days Since Latest Data\" FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -2199,7 +2199,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN LOWER(COALESCE(model, '')) = 'unknown' OR LOWER(COALESCE(feature, '')) LIKE '%unknown%' THEN code_generation_activity_count ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 2) AS \"Unknown Taxonomy %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND connection_id = ${connection_id} AND scope_id = '${scope_id}'", + "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN LOWER(COALESCE(model, '')) = 'unknown' OR LOWER(COALESCE(feature, '')) LIKE '%unknown%' THEN code_generation_activity_count ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 2) AS \"Unknown Taxonomy %\" FROM _tool_copilot_metrics_by_model_feature WHERE $__timeFilter(day) AND ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}'", "refId": "A" } ], @@ -2249,7 +2249,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT * FROM (SELECT '_tool_copilot_enterprise_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_user_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_user_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_feature WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_model_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_model_feature WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_seats' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_seats WHERE connection_id = ${connection_id} AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')) AS data_volume ORDER BY row_count DESC NULLS LAST", + "rawSql": "SELECT * FROM (SELECT '_tool_copilot_enterprise_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_user_daily_metrics' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_user_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_feature WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_metrics_by_model_feature' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_metrics_by_model_feature WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' UNION ALL SELECT '_tool_copilot_seats' AS table_name, COUNT(*) AS row_count FROM _tool_copilot_seats WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND ('${scope_id}' = '' OR organization = '${scope_id}' OR organization = '')) AS data_volume ORDER BY row_count DESC NULLS LAST", "refId": "A" } ], @@ -2272,14 +2272,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_enterprise_daily_metrics ORDER BY 1", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_enterprise_daily_metrics ORDER BY 1 NULLS FIRST", "hide": 0, "includeAll": false, "label": "Connection ID", "multi": false, "name": "connection_id", "options": [], - "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC NULLS LAST", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2293,14 +2293,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT id as scope_id FROM _tool_copilot_scopes WHERE connection_id = ${connection_id} ORDER BY 1", + "definition": "SELECT DISTINCT id AS scope_id FROM _tool_copilot_scopes WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') ORDER BY 1 NULLS FIRST", "hide": 0, "includeAll": false, "label": "Scope ID", "multi": false, "name": "scope_id", "options": [], - "query": "SELECT DISTINCT id as scope_id FROM _tool_copilot_scopes WHERE connection_id = ${connection_id} ORDER BY 1", + "query": "SELECT DISTINCT id AS scope_id FROM _tool_copilot_scopes WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') ORDER BY 1 NULLS FIRST", "refresh": 2, "regex": "", "skipUrlSync": false, @@ -2315,7 +2315,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitHub Copilot Adoption (PostgreSQL)", + "title": "GitHub Copilot Adoption", "uid": "copilot_adoption-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json b/grafana/dashboards/postgresql/github-copilot-dora-correlation.json similarity index 59% rename from grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json rename to grafana/dashboards/postgresql/github-copilot-dora-correlation.json index 699d1739afc..68b5240dc57 100644 --- a/grafana/dashboards/postgresql/GithubCopilotDORACorrelation.json +++ b/grafana/dashboards/postgresql/github-copilot-dora-correlation.json @@ -153,7 +153,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, active_users, total_seats, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, ROUND(AVG(adoption_pct), 1) AS adoption_pct, ROUND(AVG(active_users), 0) AS avg_active_users, ROUND(AVG(total_seats), 0) AS avg_total_seats FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, adoption_pct AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, active_users, total_seats, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, ROUND(CAST(AVG(adoption_pct) AS DECIMAL), 1) AS adoption_pct, ROUND(CAST(AVG(active_users) AS DECIMAL), 0) AS avg_active_users, ROUND(CAST(AVG(total_seats) AS DECIMAL), 0) AS avg_total_seats FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, adoption_pct AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "A" } ], @@ -274,7 +274,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _correlation_data AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start WHERE NOT aw.adoption_pct IS NULL AND NOT pm.avg_cycle_time_hours IS NULL), _stats AS (SELECT COUNT(*) AS n, AVG(adoption_pct) AS mean_x, AVG(avg_cycle_time_hours) AS mean_y, STDDEV_POP(adoption_pct) AS stddev_x, STDDEV_POP(avg_cycle_time_hours) AS stddev_y FROM _correlation_data), _pearson AS (SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.stddev_x = 0 OR s.stddev_y = 0 THEN 0 ELSE (SELECT CAST(SUM((cd.adoption_pct - s.mean_x) * (cd.avg_cycle_time_hours - s.mean_y)) AS NUMERIC) / NULLIF((s.n * s.stddev_x * s.stddev_y), 0) FROM _correlation_data AS cd) END AS r FROM _stats AS s) SELECT ROUND(r, 2) AS value FROM _pearson", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _correlation_data AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start WHERE NOT aw.adoption_pct IS NULL AND NOT pm.avg_cycle_time_hours IS NULL), _stats AS (SELECT COUNT(*) AS n, AVG(adoption_pct) AS mean_x, AVG(avg_cycle_time_hours) AS mean_y, STDDEV_POP(adoption_pct) AS stddev_x, STDDEV_POP(avg_cycle_time_hours) AS stddev_y FROM _correlation_data), _pearson AS (SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.stddev_x = '0' OR s.stddev_y = '0' THEN 0 ELSE (SELECT CAST(SUM((cd.adoption_pct - s.mean_x) * (cd.avg_cycle_time_hours - s.mean_y)) AS NUMERIC) / NULLIF((s.n * s.stddev_x * s.stddev_y), 0) FROM _correlation_data AS cd) END AS r FROM _stats AS s) SELECT ROUND(r, 2) AS value FROM _pearson", "refId": "A" } ], @@ -345,7 +345,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT daily_active_users AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1) AS _ent UNION ALL SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' ORDER BY date DESC NULLS LAST LIMIT 1) AS _org LIMIT 1", + "rawSql": "SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT daily_active_users AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY day DESC NULLS LAST LIMIT 1) AS _ent UNION ALL SELECT ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS value FROM (SELECT total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' ORDER BY date DESC NULLS LAST LIMIT 1) AS _org LIMIT 1", "refId": "A" } ], @@ -460,7 +460,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct >= 50 THEN 'high' ELSE 'low' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start), _tier_averages AS (SELECT adoption_tier, AVG(avg_cycle_time_hours) AS avg_cycle_time FROM _adoption_pr_joined GROUP BY adoption_tier) SELECT ROUND(CAST(((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'high') - (SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low'), 0), 0) * 100, 1) AS value", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.week_start, aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct >= 50 THEN 'high' ELSE 'low' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start), _tier_averages AS (SELECT adoption_tier, AVG(avg_cycle_time_hours) AS avg_cycle_time FROM _adoption_pr_joined GROUP BY adoption_tier) SELECT ROUND(CAST(((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'high') - (SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cycle_time FROM _tier_averages WHERE adoption_tier = 'low'), 0), 0) * 100, 1) AS value", "refId": "A" } ], @@ -602,14 +602,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "Adoption" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(avg_cycle_time_hours, 1) AS \"PR Cycle Time (hrs)\" FROM _pr_metrics_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(avg_cycle_time_hours, 1) AS \"PR Cycle Time (hrs)\" FROM _pr_metrics_weekly ORDER BY week_start NULLS FIRST", "refId": "PRTime" } ], @@ -686,7 +686,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct < 25 THEN '1. <25%' WHEN aw.adoption_pct < 50 THEN '2. 25-50%' WHEN aw.adoption_pct < 75 THEN '3. 50-75%' ELSE '4. >75%' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start) SELECT adoption_tier AS metric, ROUND(AVG(avg_cycle_time_hours), 1) AS value FROM _adoption_pr_joined GROUP BY adoption_tier ORDER BY adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_pr_joined AS (SELECT aw.adoption_pct, pm.avg_cycle_time_hours, CASE WHEN aw.adoption_pct < 25 THEN '1. <25%' WHEN aw.adoption_pct < 50 THEN '2. 25-50%' WHEN aw.adoption_pct < 75 THEN '3. 50-75%' ELSE '4. >75%' END AS adoption_tier FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start) SELECT adoption_tier AS metric, ROUND(CAST(AVG(avg_cycle_time_hours) AS DECIMAL), 1) AS value FROM _adoption_pr_joined GROUP BY adoption_tier ORDER BY adoption_tier NULLS FIRST", "refId": "A" } ], @@ -798,7 +798,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT ROUND(aw.adoption_pct, 1) AS adoption_pct, ROUND(pm.avg_cycle_time_hours, 1) AS cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start ORDER BY aw.adoption_pct NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_metrics_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day') SELECT ROUND(aw.adoption_pct, 1) AS adoption_pct, ROUND(pm.avg_cycle_time_hours, 1) AS cycle_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_metrics_weekly AS pm ON aw.week_start = pm.week_start ORDER BY aw.adoption_pct NULLS FIRST", "refId": "A" } ], @@ -857,7 +857,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _low_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct < 50) SELECT 'Coding Time' AS component, ROUND(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks)", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _low_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct < 50) SELECT 'Coding Time' AS component, ROUND(CAST(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _low_adoption_weeks)", "refId": "A" } ], @@ -916,7 +916,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _high_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct >= 50) SELECT 'Coding Time' AS component, ROUND(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS value FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks)", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _high_adoption_weeks AS (SELECT week_start FROM _adoption_weekly WHERE adoption_pct >= 50) SELECT 'Coding Time' AS component, ROUND(CAST(CAST(AVG(pr_coding_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Pickup Time' AS component, ROUND(CAST(CAST(AVG(pr_pickup_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks) UNION ALL SELECT 'Review Time' AS component, ROUND(CAST(CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS value FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' IN (SELECT week_start FROM _high_adoption_weeks)", "refId": "A" } ], @@ -1071,7 +1071,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", COALESCE(dw.deploy_count, 0) AS \"Deployments\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", COALESCE(dw.deploy_count, 0) AS \"Deployments\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -1144,7 +1144,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(COALESCE(dw.deploy_count, 0)), 1) AS \"Deploys/Week\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(CAST(AVG(COALESCE(dw.deploy_count, 0)) AS DECIMAL), 1) AS \"Deploys/Week\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -1220,7 +1220,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * deploy_count) - SUM(adoption_pct) * SUM(deploy_count)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(deploy_count * deploy_count) - SUM(deploy_count) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined WHERE NOT adoption_pct IS NULL", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start) SELECT ROUND(CAST(CAST((COUNT(*) * SUM(adoption_pct * deploy_count) - SUM(adoption_pct) * SUM(deploy_count)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - POWER(SUM(adoption_pct), 2)) * (COUNT(*) * SUM(deploy_count * deploy_count) - POWER(SUM(deploy_count), 2))), 0), 0) AS DECIMAL), 2) AS correlation_r FROM _joined WHERE NOT adoption_pct IS NULL", "refId": "A" } ], @@ -1296,7 +1296,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _adoption_deploy AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start), _tier_avg AS (SELECT tier, AVG(deploy_count) AS avg_deploys FROM _adoption_deploy GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_deploys FROM _tier_avg WHERE tier = 'high') - (SELECT avg_deploys FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_deploys FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count FROM cicd_deployment_commits WHERE result = 'SUCCESS' AND $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_deploy AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, COALESCE(dw.deploy_count, 0) AS deploy_count FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start), _tier_avg AS (SELECT tier, AVG(deploy_count) AS avg_deploys FROM _adoption_deploy GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_deploys FROM _tier_avg WHERE tier = 'high') - (SELECT avg_deploys FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_deploys FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", "refId": "A" } ], @@ -1369,7 +1369,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, adoption_pct, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, AVG(CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0)) AS cycle_time_hrs FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND NOT pr_merged_date IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _cfr_weekly AS (SELECT week_start, ROUND(CAST(failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(deploy_count, 0), 0), 1) AS cfr_pct FROM _deploy_weekly), _mttr_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _tier_metrics AS (SELECT at.tier, ROUND(AVG(pw.cycle_time_hrs), 1) AS pr_cycle_time, ROUND(AVG(dw.deploy_count), 1) AS deploy_freq, ROUND(AVG(cfr.cfr_pct), 1) AS cfr_pct, ROUND(AVG(mttr.mttr_hours), 1) AS mttr_hours FROM _adoption_tiers AS at LEFT JOIN _pr_weekly AS pw ON at.week_start = pw.week_start LEFT JOIN _deploy_weekly AS dw ON at.week_start = dw.week_start LEFT JOIN _cfr_weekly AS cfr ON at.week_start = cfr.week_start LEFT JOIN _mttr_weekly AS mttr ON at.week_start = mttr.week_start GROUP BY at.tier) SELECT tier AS \"Adoption Tier\", COALESCE(pr_cycle_time, 0) AS \"PR Cycle Time (hrs)\", COALESCE(deploy_freq, 0) AS \"Deploys/Week\", COALESCE(cfr_pct, 0) AS \"CFR %\", COALESCE(mttr_hours, 0) AS \"MTTR (hours)\" FROM _tier_metrics ORDER BY FIELD(tier, '<25%', '25-50%', '50-75%', '>75%') NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _adoption_tiers AS (SELECT week_start, \"adoption_pct\", CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG(CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0)) AS cycle_time_hrs FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND NOT pr_merged_date IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS deploy_count, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day'), _cfr_weekly AS (SELECT week_start, ROUND(CAST(failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(deploy_count, 0), 0), 1) AS cfr_pct FROM _deploy_weekly), _mttr_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day'), _tier_metrics AS (SELECT at.tier, ROUND(CAST(AVG(pw.cycle_time_hrs) AS DECIMAL), 1) AS pr_cycle_time, ROUND(CAST(AVG(dw.deploy_count) AS DECIMAL), 1) AS deploy_freq, ROUND(CAST(AVG(cfr.cfr_pct) AS DECIMAL), 1) AS cfr_pct, ROUND(CAST(AVG(mttr.mttr_hours) AS DECIMAL), 1) AS mttr_hours FROM _adoption_tiers AS at LEFT JOIN _pr_weekly AS pw ON at.week_start = pw.week_start LEFT JOIN _deploy_weekly AS dw ON at.week_start = dw.week_start LEFT JOIN _cfr_weekly AS cfr ON at.week_start = cfr.week_start LEFT JOIN _mttr_weekly AS mttr ON at.week_start = mttr.week_start GROUP BY at.tier) SELECT tier AS \"Adoption Tier\", COALESCE(pr_cycle_time, 0) AS \"PR Cycle Time (hrs)\", COALESCE(deploy_freq, 0) AS \"Deploys/Week\", COALESCE(cfr_pct, 0) AS \"CFR %\", COALESCE(mttr_hours, 0) AS \"MTTR (hours)\" FROM _tier_metrics ORDER BY CASE tier WHEN '<25%' THEN 1 WHEN '25-50%' THEN 2 WHEN '50-75%' THEN 3 WHEN '>75%' THEN 4 END NULLS FIRST", "refId": "A" } ], @@ -1524,7 +1524,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -1601,7 +1601,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(CAST(AVG(CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS DECIMAL), 1) AS \"CFR %\" FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -1677,7 +1677,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * cfr_pct) - SUM(adoption_pct) * SUM(cfr_pct)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(cfr_pct * cfr_pct) - SUM(cfr_pct) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0) SELECT ROUND(CAST(CAST((COUNT(*) * SUM(adoption_pct * cfr_pct) - SUM(adoption_pct) * SUM(cfr_pct)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - POWER(SUM(adoption_pct), 2)) * (COUNT(*) * SUM(cfr_pct * cfr_pct) - POWER(SUM(cfr_pct), 2))), 0), 0) AS DECIMAL), 2) AS correlation_r FROM _joined", "refId": "A" } ], @@ -1753,7 +1753,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM finished_date) - 1) * INTERVAL '1 day'), _adoption_cfr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0), _tier_avg AS (SELECT tier, AVG(cfr_pct) AS avg_cfr FROM _adoption_cfr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_cfr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_cfr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cfr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _deployments_weekly AS (SELECT CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(*) AS total_deploys, SUM(CASE WHEN result = 'FAILURE' THEN 1 ELSE 0 END) AS failed_deploys FROM cicd_deployment_commits WHERE $__timeFilter(finished_date) GROUP BY CAST(finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_cfr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, (CAST(dw.failed_deploys * 100.0 AS NUMERIC) / NULLIF(NULLIF(dw.total_deploys, 0), 0)) AS cfr_pct FROM _adoption_weekly AS aw LEFT JOIN _deployments_weekly AS dw ON aw.week_start = dw.week_start WHERE dw.total_deploys > 0), _tier_avg AS (SELECT tier, AVG(cfr_pct) AS avg_cfr FROM _adoption_cfr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_cfr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_cfr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_cfr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", "refId": "A" } ], @@ -1908,14 +1908,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(adoption_pct, 1) AS \"Adoption %\" FROM _adoption_weekly ORDER BY week_start NULLS FIRST", "refId": "Adoption" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(mttr_hours, 1) AS \"MTTR (hours)\" FROM _incidents_weekly ORDER BY week_start NULLS FIRST", + "rawSql": "WITH _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day') SELECT week_start AS time, ROUND(mttr_hours, 1) AS \"MTTR (hours)\" FROM _incidents_weekly ORDER BY week_start NULLS FIRST", "refId": "MTTR" } ], @@ -1992,7 +1992,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(iw.mttr_hours), 1) AS \"MTTR (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start WHERE NOT iw.mttr_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(CAST(AVG(iw.mttr_hours) AS DECIMAL), 1) AS \"MTTR (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start WHERE NOT iw.mttr_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -2068,7 +2068,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * mttr_hours) - SUM(adoption_pct) * SUM(mttr_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(mttr_hours * mttr_hours) - SUM(mttr_hours) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start) SELECT ROUND(CAST(CAST((COUNT(*) * SUM(adoption_pct * mttr_hours) - SUM(adoption_pct) * SUM(mttr_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - POWER(SUM(adoption_pct), 2)) * (COUNT(*) * SUM(mttr_hours * mttr_hours) - POWER(SUM(mttr_hours), 2))), 0), 0) AS DECIMAL), 2) AS correlation_r FROM _joined", "refId": "A" } ], @@ -2144,7 +2144,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM resolution_date) - 1) * INTERVAL '1 day'), _adoption_mttr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start), _tier_avg AS (SELECT tier, AVG(mttr_hours) AS avg_mttr FROM _adoption_mttr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_mttr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_mttr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_mttr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _incidents_weekly AS (SELECT CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, AVG((EXTRACT(EPOCH FROM (resolution_date - created_date))/3600)) AS mttr_hours FROM issues WHERE type = 'INCIDENT' AND NOT resolution_date IS NULL AND $__timeFilter(resolution_date) GROUP BY CAST(resolution_date AS DATE) - (EXTRACT(ISODOW FROM CAST(resolution_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_mttr AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, iw.mttr_hours FROM _adoption_weekly AS aw INNER JOIN _incidents_weekly AS iw ON aw.week_start = iw.week_start), _tier_avg AS (SELECT tier, AVG(mttr_hours) AS avg_mttr FROM _adoption_mttr GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_mttr FROM _tier_avg WHERE tier = 'high') - (SELECT avg_mttr FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_mttr FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", "refId": "A" } ], @@ -2234,7 +2234,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(AVG(prw.avg_review_time_hours), 1) AS \"Review Time (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start WHERE NOT prw.avg_review_time_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct, CASE WHEN AVG(adoption_pct) < 25 THEN '1: <25%' WHEN AVG(adoption_pct) < 50 THEN '2: 25-50%' WHEN AVG(adoption_pct) < 75 THEN '3: 50-75%' ELSE '4: >75%' END AS adoption_tier FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day') SELECT aw.adoption_tier AS \"Adoption Tier\", ROUND(CAST(AVG(prw.avg_review_time_hours) AS DECIMAL), 1) AS \"Review Time (hours)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start WHERE NOT prw.avg_review_time_hours IS NULL GROUP BY aw.adoption_tier ORDER BY aw.adoption_tier NULLS FIRST", "refId": "A" } ], @@ -2376,7 +2376,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(prw.avg_review_time_hours, 1) AS \"Review Time (h)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start ORDER BY aw.week_start NULLS FIRST", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day') SELECT EXTRACT(EPOCH FROM aw.week_start) AS time_sec, aw.adoption_pct AS \"Adoption %\", ROUND(prw.avg_review_time_hours, 1) AS \"Review Time (h)\" FROM _adoption_weekly AS aw LEFT JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start ORDER BY aw.week_start NULLS FIRST", "refId": "A" } ], @@ -2452,7 +2452,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _adoption_review AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start), _tier_avg AS (SELECT tier, AVG(avg_review_time_hours) AS avg_review FROM _adoption_review GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_review FROM _tier_avg WHERE tier = 'high') - (SELECT avg_review FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_review FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _adoption_review AS (SELECT CASE WHEN aw.adoption_pct < 50 THEN 'low' ELSE 'high' END AS tier, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start), _tier_avg AS (SELECT tier, AVG(avg_review_time_hours) AS avg_review FROM _adoption_review GROUP BY tier) SELECT ROUND(CAST(((SELECT avg_review FROM _tier_avg WHERE tier = 'high') - (SELECT avg_review FROM _tier_avg WHERE tier = 'low')) AS NUMERIC) / NULLIF(NULLIF((SELECT avg_review FROM _tier_avg WHERE tier = 'low'), 0), 0) * 100, 1) AS change_pct", "refId": "A" } ], @@ -2528,7 +2528,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE project_name = ${project:sqlstring} AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start) SELECT ROUND(CAST((COUNT(*) * SUM(adoption_pct * avg_review_time_hours) - SUM(adoption_pct) * SUM(avg_review_time_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - SUM(adoption_pct) ^ 2) * (COUNT(*) * SUM(avg_review_time_hours * avg_review_time_hours) - SUM(avg_review_time_hours) ^ 2)), 0), 0), 2) AS correlation_r FROM _joined", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(day) GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' AND $__timeFilter(date)) AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _pr_review_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_review_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_review_time_hours FROM project_pr_metrics WHERE ('${project:sqlstring}' = '' OR project_name::text = '${project:sqlstring}') AND $__timeFilter(pr_merged_date) AND pr_review_time > 0 GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT aw.adoption_pct, prw.avg_review_time_hours FROM _adoption_weekly AS aw INNER JOIN _pr_review_weekly AS prw ON aw.week_start = prw.week_start) SELECT ROUND(CAST(CAST((COUNT(*) * SUM(adoption_pct * avg_review_time_hours) - SUM(adoption_pct) * SUM(avg_review_time_hours)) AS NUMERIC) / NULLIF(NULLIF(SQRT((COUNT(*) * SUM(adoption_pct * adoption_pct) - POWER(SUM(adoption_pct), 2)) * (COUNT(*) * SUM(avg_review_time_hours * avg_review_time_hours) - POWER(SUM(avg_review_time_hours), 2))), 0), 0) AS DECIMAL), 2) AS correlation_r FROM _joined", "refId": "A" } ], @@ -2607,7 +2607,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE s.connection_id = ${connection_id} AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE sc.connection_id = ${connection_id} AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - INTERVAL 'WEEKDAY DAY' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - INTERVAL 'WEEKDAY DAY'), _adoption_tiers AS (SELECT week_start, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly) SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS \"Bugs/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "WITH _copilot_adoption AS (SELECT metric_date, ROUND(CAST(active_users * 100.0 AS NUMERIC) / NULLIF(NULLIF(total_seats, 0), 0), 1) AS adoption_pct FROM (SELECT day AS metric_date, SUM(daily_active_users) AS active_users, (SELECT COUNT(*) FROM _tool_copilot_seats AS s WHERE ('${connection_id}' = '' OR s.connection_id::text = '${connection_id}') AND COALESCE(s.organization, '') = COALESCE((SELECT sc.organization FROM _tool_copilot_scopes AS sc WHERE ('${connection_id}' = '' OR sc.connection_id::text = '${connection_id}') AND sc.id = '${scope_id}' LIMIT 1), '')) AS total_seats FROM _tool_copilot_enterprise_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}' GROUP BY day UNION ALL SELECT date AS metric_date, total_active_users AS active_users, seat_total AS total_seats FROM _tool_copilot_org_daily_metrics WHERE ('${connection_id}' = '' OR connection_id::text = '${connection_id}') AND scope_id = '${scope_id}') AS _unified), _adoption_weekly AS (SELECT metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day' AS week_start, AVG(adoption_pct) AS adoption_pct FROM _copilot_adoption GROUP BY metric_date - (EXTRACT(ISODOW FROM metric_date) - 1) * INTERVAL '1 day'), _adoption_tiers AS (SELECT week_start, CASE WHEN adoption_pct < 25 THEN '<25%' WHEN adoption_pct < 50 THEN '25-50%' WHEN adoption_pct < 75 THEN '50-75%' ELSE '>75%' END AS tier FROM _adoption_weekly) SELECT 'N/A - Configure SonarQube' AS \"Tier\", 0 AS \"Bugs/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], @@ -2673,7 +2673,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS \"Code Smells/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "SELECT 'N/A - Configure SonarQube' AS \"Tier\", 0 AS \"Code Smells/File\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], @@ -2739,7 +2739,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS Complexity FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "SELECT 'N/A - Configure SonarQube' AS \"Tier\", 0 AS \"Complexity\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], @@ -2808,7 +2808,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'N/A - Configure SonarQube' AS Tier, 0 AS Coverage FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", + "rawSql": "SELECT 'N/A - Configure SonarQube' AS \"Tier\", 0 AS \"Coverage\" FROM (SELECT 1) AS d WHERE NOT EXISTS(SELECT 1 FROM cq_file_metrics LIMIT 1)", "refId": "A" } ], @@ -2833,14 +2833,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY 1", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY 1 NULLS FIRST", "hide": 0, "includeAll": false, "label": "Connection ID", "multi": false, "name": "connection_id", "options": [], - "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC NULLS LAST", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2854,14 +2854,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS UNSIGNED) ORDER BY 1", + "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS BIGINT) ORDER BY 1 NULLS FIRST", "hide": 0, "includeAll": false, "label": "Scope ID (Organization)", "multi": false, "name": "scope_id", "options": [], - "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS UNSIGNED) ORDER BY 1", + "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${connection_id}' AS BIGINT) ORDER BY 1 NULLS FIRST", "refresh": 2, "regex": "", "skipUrlSync": false, @@ -2875,14 +2875,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1", + "definition": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1 NULLS FIRST", "hide": 0, "includeAll": false, "label": "Project", "multi": false, "name": "project", "options": [], - "query": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1", + "query": "SELECT DISTINCT project_name FROM project_pr_metrics ORDER BY 1 NULLS FIRST", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2924,7 +2924,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitHub Copilot + DORA Correlation (PostgreSQL)", + "title": "GitHub Copilot + DORA Correlation", "uid": "copilot_impact-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json b/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json similarity index 70% rename from grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json rename to grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json index 75bc25c3fe4..fa9e8b8090f 100644 --- a/grafana/dashboards/postgresql/GithubReleaseQualityAndContributionAnalysis.json +++ b/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json @@ -118,7 +118,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug distribution in major versions */ WITH bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND i.type = 'BUG') SELECT CONCAT(SUBSTRING_INDEX(biet.tag_name, '.', 3), '.x') AS minor_version, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the bug distribution in major versions */ WITH bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.type = 'BUG') SELECT SPLIT_PART(biet.tag_name, '.', 3) || '.x' AS minor_version, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -245,7 +245,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id /* distinct new_ref_id, old_ref_id */ FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs_of_tags AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, COUNT(*) AS bug_count /* SUBSTRING_INDEX(rid.new_ref_id,':', 3) as repo_id, */ FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' GROUP BY 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE p.base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) AS repo_id, pr.id AS pull_request_id, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT SUBSTRING_INDEX(new_ref_id, ':', -1) FROM _last_5_tags) GROUP BY 1, 2, 3), _pr_worktype AS (SELECT DISTINCT pri.pull_request_id, i.type FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _pr_elco_and_worktype AS (SELECT ccop.tag_name, CAST(SUM(CASE WHEN pw.type = 'BUG' THEN commit_count ELSE 0 END) AS NUMERIC) / NULLIF(SUM(commit_count), 0) AS cost_percentage FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_worktype AS pw ON ccop.pull_request_id = pw.pull_request_id GROUP BY 1) SELECT bot.tag_name, bot.bug_count, peaw.cost_percentage AS \"cost_percentage(bugfixing commits/total commits)\" FROM _bugs_of_tags AS bot JOIN _pr_elco_and_worktype AS peaw ON bot.tag_name = peaw.tag_name ORDER BY 1 NULLS FIRST", + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id /* distinct new_ref_id, old_ref_id */ FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs_of_tags AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, COUNT(*) AS bug_count /* SPLIT_PART(rid.new_ref_id, ':', 3) as repo_id, */ FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' /* GROUP BY 1, 2 */ GROUP BY 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE p.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, SPLIT_PART(rcd.new_ref_id, ':', 4) AS repo_id, pr.id AS pull_request_id, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) GROUP BY 1, 2, 3), _pr_worktype AS (SELECT DISTINCT pri.pull_request_id, i.type FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _pr_elco_and_worktype AS (SELECT ccop.tag_name, CAST(SUM(CASE WHEN pw.type = 'BUG' THEN commit_count ELSE 0 END) AS NUMERIC) / NULLIF(SUM(commit_count), 0) AS cost_percentage FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_worktype AS pw ON ccop.pull_request_id = pw.pull_request_id GROUP BY 1) SELECT bot.tag_name, bot.bug_count, peaw.cost_percentage AS \"cost_percentage(bugfixing commits/total commits)\" FROM _bugs_of_tags AS bot JOIN _pr_elco_and_worktype AS peaw ON bot.tag_name = peaw.tag_name ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -331,7 +331,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -487,7 +487,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -573,7 +573,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT CONCAT(biet.tag_name, ' ', CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END) AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -735,7 +735,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT DISTINCT b.name AS repo_name, SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.issue_key AS issue_key, i.title, i.assignee_name, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id JOIN boards AS b ON SUBSTRING_INDEX(rid.new_ref_id, ':', 4) = b.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT SUBSTRING_INDEX(new_ref_id, ':', -1) FROM _last_5_tags) AND i.type = 'BUG' ORDER BY tag_name DESC NULLS LAST", + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT DISTINCT b.name AS repo_name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, i.assignee_name, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) AND i.type = 'BUG' ORDER BY tag_name DESC NULLS LAST", "refId": "A", "select": [ [ @@ -822,7 +822,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT CASE WHEN component = '' THEN 'unlabeled' ELSE 'labeled' END AS component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT CASE WHEN component = '' THEN 'unlabeled' ELSE 'labeled' END AS component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -909,7 +909,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT SUBSTRING_INDEX(rid.new_ref_id, 'refs/', -1) AS tag_name, SUBSTRING_INDEX(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet WHERE component <> '' GROUP BY 1", + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet WHERE component <> '' GROUP BY 1", "refId": "A", "select": [ [ @@ -997,7 +997,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the % of contributors who fixed 80% of bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST), _bug_fixed_count AS (SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1), _bug_fixed_count_running_total AS (SELECT *, SUM(bug_fixed_count) OVER (ORDER BY bug_fixed_count DESC NULLS LAST) AS running_total FROM _bug_fixed_count), _percentile AS (SELECT pr_author, bug_fixed_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(bug_fixed_count) OVER (), 0) AS cumulative_percentage FROM _bug_fixed_count_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN pr_author ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"% of contributors who fixed 80% of the bugs\" FROM _percentile", + "rawSql": "/* Get the % of contributors who fixed 80% of bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST), _bug_fixed_count AS (SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1), _bug_fixed_count_running_total AS (SELECT *, SUM(bug_fixed_count) OVER (ORDER BY bug_fixed_count DESC NULLS LAST) AS running_total FROM _bug_fixed_count), _percentile AS (SELECT pr_author, bug_fixed_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(bug_fixed_count) OVER (), 0) AS cumulative_percentage FROM _bug_fixed_count_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN pr_author ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"% of contributors who fixed 80% of the bugs\" FROM _percentile", "refId": "A", "select": [ [ @@ -1095,7 +1095,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST) SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST) SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1185,7 +1185,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the avg bug age in history */ SELECT CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "/* Get the avg bug age in history */ SELECT CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1289,7 +1289,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug age in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.id, i.lead_time_minutes FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bugs_percentile AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY tag_name ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM _bugs ORDER BY 1 NULLS FIRST), _avg_bug_age AS (SELECT tag_name, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM _bugs_percentile GROUP BY 1), _50th_bug_age AS (SELECT tag_name, CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"50th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5 GROUP BY 1) SELECT aba.*, eba.\"50th_bug_age\" FROM _avg_bug_age AS aba JOIN _50th_bug_age AS eba ON aba.tag_name = eba.tag_name", + "rawSql": "/* Get the bug age in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.id, i.lead_time_minutes FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bugs_percentile AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY tag_name ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM _bugs ORDER BY 1 NULLS FIRST), _avg_bug_age AS (SELECT tag_name, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM _bugs_percentile GROUP BY 1), _50th_bug_age AS (SELECT tag_name, CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"50th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5 GROUP BY 1) SELECT aba.*, eba.\"50th_bug_age\" FROM _avg_bug_age AS aba JOIN _50th_bug_age AS eba ON aba.tag_name = eba.tag_name", "refId": "A", "select": [ [ @@ -1547,7 +1547,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT b.name, SUBSTRING_INDEX(rid.new_ref_id, 'tags/', -1) AS tag_name, i.issue_key AS issue_key, i.title, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id JOIN boards AS b ON SUBSTRING_INDEX(rid.new_ref_id, ':', 4) = b.id WHERE SUBSTRING_INDEX(rid.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rid.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bug_age_rank AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY tag_name ORDER BY lead_time_in_days DESC NULLS LAST) AS bug_age_rank FROM _bugs) SELECT name, tag_name, issue_key, title, lead_time_in_days, url FROM _bug_age_rank WHERE bug_age_rank <= 10 ORDER BY tag_name NULLS FIRST, lead_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT b.name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bug_age_rank AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY tag_name ORDER BY lead_time_in_days DESC NULLS LAST) AS bug_age_rank FROM _bugs) SELECT name, tag_name, issue_key, title, lead_time_in_days, url FROM _bug_age_rank WHERE bug_age_rank <= 10 ORDER BY tag_name NULLS FIRST, lead_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1696,7 +1696,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id = ANY(ARRAY[${repo_id}]::text[]) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN '>=avg_bug_age' ELSE ' 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", + "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN '>=avg_bug_age' ELSE ' 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", "refId": "A", "select": [ [ @@ -1858,7 +1858,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all backlog bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(3600, 0) AS queue_time_in_days, CONCAT(b.url, '/', i.issue_key) AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE i.type = 'BUG' AND i.status <> 'DONE' AND b.id = ANY(ARRAY[${repo_id}]::text[]) ORDER BY queue_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the queue time of all backlog bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(3600, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE i.type = 'BUG' AND i.status <> 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY queue_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1967,7 +1967,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'refs/tags/', -1) AS tag_name, SUBSTRING_INDEX(rcd.old_ref_id, 'refs/tags/', -1) AS old_tag_name, COUNT(*) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2 ORDER BY 1 NULLS FIRST", + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS old_tag_name, COUNT(*) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2078,7 +2078,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'refs/tags/', -1) AS new_tag_name, SUBSTRING_INDEX(rcd.old_ref_id, 'refs/tags/', -1) AS compared_tag_name, c.sha, c.message, c.additions, c.deletions, c.author_name FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) ORDER BY 1 DESC NULLS LAST", + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS new_tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS compared_tag_name, c.sha, c.message, c.additions, c.deletions, c.author_name FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) ORDER BY 1 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -2195,7 +2195,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2312,7 +2312,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2429,7 +2429,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[])), _commit_count_of_pr AS (SELECT SUBSTRING_INDEX(rcd.new_ref_id, 'tags/', -1) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SUBSTRING_INDEX(rcd.new_ref_id, ':', 4) IN (${repo_id}) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ SUBSTRING_INDEX(rcd.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> 0), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2517,7 +2517,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get each contributor's work in bugfixing in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5), _author_commits AS (SELECT c.author_name, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND SUBSTRING_INDEX(rcf.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1), _author_commits_running_total AS (SELECT *, SUM(commit_count) OVER (ORDER BY commit_count DESC NULLS LAST) AS running_total FROM _author_commits), _percentile AS (SELECT author_name, commit_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(commit_count) OVER (), 0) AS cumulative_percentage FROM _author_commits_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN author_name ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"contributors who contributed 80% of dev_eq\" FROM _percentile", + "rawSql": "/* Get each contributor's work in bugfixing in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _author_commits AS (SELECT c.author_name, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1), _author_commits_running_total AS (SELECT *, SUM(commit_count) OVER (ORDER BY commit_count DESC NULLS LAST) AS running_total FROM _author_commits), _percentile AS (SELECT author_name, commit_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(commit_count) OVER (), 0) AS cumulative_percentage FROM _author_commits_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN author_name ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"contributors who contributed 80% of dev_eq\" FROM _percentile", "refId": "A", "select": [ [ @@ -2612,7 +2612,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ SUBSTRING_INDEX(new_ref_id, ':', -1) AS new_ref_id, SUBSTRING_INDEX(old_ref_id, ':', -1) AS old_ref_id FROM refs_commits_diffs WHERE SUBSTRING_INDEX(new_ref_id, ':', 4) IN (${repo_id}) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT c.author_name, COUNT(c.sha) AS total_dev_eq FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SUBSTRING_INDEX(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND SUBSTRING_INDEX(rcf.new_ref_id, ':', -1) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "rawSql": "WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT c.author_name, COUNT(c.sha) AS total_dev_eq FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -2682,7 +2682,7 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from repos", + "definition": "SELECT name || '--' || id AS text FROM repos", "description": null, "error": null, "hide": 0, @@ -2691,7 +2691,7 @@ "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '--', id) as text from repos", + "query": "SELECT name || '--' || id AS text FROM repos", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2706,7 +2706,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "GitHub_Release_Quality_and_Contribution_Analysis (PostgreSQL)", + "title": "GitHub Release Quality and Contribution Analysis", "uid": "2xuOaQUnk4-pg", "version": 3 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/KiroCreditsDORA.json b/grafana/dashboards/postgresql/kiro-credits-dora.json similarity index 70% rename from grafana/dashboards/postgresql/KiroCreditsDORA.json rename to grafana/dashboards/postgresql/kiro-credits-dora.json index 78a0214a29c..c541b5eda05 100644 --- a/grafana/dashboards/postgresql/KiroCreditsDORA.json +++ b/grafana/dashboards/postgresql/kiro-credits-dora.json @@ -145,7 +145,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_hours FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, p.avg_cycle_hours AS y FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_cycle_hours FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, p.avg_cycle_hours AS y FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = '0' OR s.sy = '0' THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", "refId": "A" } ], @@ -301,7 +301,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_ct FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits, p.avg_ct FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _med AS (SELECT weekly_credits AS med FROM _joined ORDER BY weekly_credits NULLS FIRST LIMIT 1 OFFSET (SELECT FLOOR(CAST(COUNT(*) AS NUMERIC) / NULLIF(2, 0)) FROM _joined)) SELECT CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END AS \"Tier\", ROUND(AVG(j.avg_ct), 1) AS \"Avg Cycle Time\" FROM _joined AS j, _med AS m GROUP BY CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _pr_weekly AS (SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS avg_ct FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits, p.avg_ct FROM _kiro_weekly AS k INNER JOIN _pr_weekly AS p ON k.week_start = p.week_start), _med AS (SELECT weekly_credits AS med FROM _joined ORDER BY weekly_credits NULLS FIRST LIMIT 1 OFFSET (SELECT FLOOR(CAST(COUNT(*) AS NUMERIC) / NULLIF(2, 0)) FROM _joined)) SELECT CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END AS \"Tier\", ROUND(CAST(AVG(j.avg_ct) AS DECIMAL), 1) AS \"Avg Cycle Time\" FROM _joined AS j, _med AS m GROUP BY CASE WHEN j.weekly_credits >= m.med THEN 'High AI Usage' ELSE 'Low AI Usage' END", "refId": "A" } ], @@ -377,7 +377,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits Used\", COUNT(DISTINCT user_id) AS \"Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS \"Credits Used\", COUNT(DISTINCT user_id) AS \"Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -440,7 +440,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' AS time, ROUND(CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0), 1) AS \"Avg Cycle Time (hrs)\", COUNT(*) AS \"PRs Merged\" FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM pr_merged_date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' AS time, ROUND(CAST(CAST(AVG(pr_cycle_time) AS NUMERIC) / NULLIF(60.0, 0) AS DECIMAL), 1) AS \"Avg Cycle Time (hrs)\", COUNT(*) AS \"PRs Merged\" FROM project_pr_metrics WHERE NOT pr_merged_date IS NULL AND NOT pr_cycle_time IS NULL AND $__timeFilter(pr_merged_date) GROUP BY CAST(pr_merged_date AS DATE) - (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -515,7 +515,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, d.deploys AS y FROM _kiro_weekly AS k INNER JOIN _deploy_weekly AS d ON k.week_start = d.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = 0 OR s.sy = 0 THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", + "rawSql": "WITH _kiro_weekly AS (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, SUM(credits_used) AS weekly_credits FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day'), _deploy_weekly AS (SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(cdc.finished_date AS DATE)) - 1) * INTERVAL '1 day' AS week_start, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(cdc.finished_date AS DATE)) - 1) * INTERVAL '1 day'), _joined AS (SELECT k.weekly_credits AS x, d.deploys AS y FROM _kiro_weekly AS k INNER JOIN _deploy_weekly AS d ON k.week_start = d.week_start), _stats AS (SELECT COUNT(*) AS n, AVG(x) AS mx, AVG(y) AS my, STDDEV_POP(x) AS sx, STDDEV_POP(y) AS sy FROM _joined) SELECT CASE WHEN s.n < 4 THEN NULL WHEN s.sx = '0' OR s.sy = '0' THEN 0 ELSE ROUND(CAST((SELECT SUM((j.x - s.mx) * (j.y - s.my)) FROM _joined AS j) AS NUMERIC) / NULLIF((s.n * s.sx * s.sy), 0), 2) END AS \"r\" FROM _stats AS s", "refId": "A" } ], @@ -578,7 +578,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(deploys) AS \"Deployments\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS deploys FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM cdc.finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(deploys) AS \"Deployments\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS deploys FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(cdc.finished_date AS DATE)) - 1) * INTERVAL '1 day' AS time, 0 AS credits, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploys FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY CAST(cdc.finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(cdc.finished_date AS DATE)) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -654,7 +654,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(cfr) AS \"Change Failure Rate\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS cfr FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day' AS time, 0 AS credits, CAST(SUM(CASE WHEN NOT i.id IS NULL THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT d.deployment_id), 0) AS cfr FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY cdc.cicd_deployment_id) AS d LEFT JOIN project_incident_deployment_relationships AS pidr ON d.deployment_id = pidr.deployment_id LEFT JOIN incidents AS i ON pidr.id = i.id GROUP BY CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM d.deployment_finished_date) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(credits) AS \"Credits Used\", SUM(cfr) AS \"Change Failure Rate\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(credits_used) AS credits, 0 AS cfr FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(d.deployment_finished_date AS DATE)) - 1) * INTERVAL '1 day' AS time, 0 AS credits, CAST(SUM(CASE WHEN NOT i.id IS NULL THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT d.deployment_id), 0) AS cfr FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY cdc.cicd_deployment_id) AS d LEFT JOIN project_incident_deployment_relationships AS pidr ON d.deployment_id = pidr.deployment_id LEFT JOIN incidents AS i ON pidr.id = i.id GROUP BY CAST(d.deployment_finished_date AS DATE) - (EXTRACT(ISODOW FROM CAST(d.deployment_finished_date AS DATE)) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -703,7 +703,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Credits + DORA Correlation (PostgreSQL)", + "title": "Kiro Credits + DORA Correlation", "uid": "kiro_credits_dora-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/LanguageAIHeatmap.json b/grafana/dashboards/postgresql/language-ai-heatmap.json similarity index 89% rename from grafana/dashboards/postgresql/LanguageAIHeatmap.json rename to grafana/dashboards/postgresql/language-ai-heatmap.json index 20812080377..f2380c3efc9 100644 --- a/grafana/dashboards/postgresql/LanguageAIHeatmap.json +++ b/grafana/dashboards/postgresql/language-ai-heatmap.json @@ -66,7 +66,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"Language\", COUNT(*) AS \"Requests\", ROUND(AVG(completions_count), 2) AS \"Avg Completions\", ROUND(AVG(left_context_length)) AS \"Avg Left Context\", ROUND(AVG(right_context_length)) AS \"Avg Right Context\", ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\", COUNT(DISTINCT user_id) AS \"Users\" FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"Language\", COUNT(*) AS \"Requests\", ROUND(CAST(AVG(completions_count) AS DECIMAL), 2) AS \"Avg Completions\", ROUND(AVG(left_context_length)) AS \"Avg Left Context\", ROUND(AVG(right_context_length)) AS \"Avg Right Context\", ROUND(AVG(left_context_length + right_context_length)) AS \"Avg Total Context\", COUNT(DISTINCT user_id) AS \"Users\" FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY \"file_extension\" ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -121,7 +121,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS metric, COUNT(*) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), file_extension ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS metric, COUNT(*) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE), \"file_extension\" ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -186,7 +186,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY \"active_file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", "refId": "A" } ], @@ -244,7 +244,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) AS time, file_extension AS metric, ROUND(AVG(left_context_length + right_context_length)) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) AND file_extension IN (SELECT file_extension FROM _tool_q_dev_completion_log GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 5) GROUP BY CAST(timestamp AS DATE), file_extension ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) AS time, file_extension AS metric, ROUND(AVG(left_context_length + right_context_length)) AS value FROM _tool_q_dev_completion_log WHERE $__timeFilter(timestamp) AND file_extension IN (SELECT file_extension FROM _tool_q_dev_completion_log GROUP BY \"file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 5) GROUP BY CAST(timestamp AS DATE), \"file_extension\" ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -270,7 +270,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Language AI Heatmap (PostgreSQL)", + "title": "Kiro Language AI Heatmap", "uid": "kiro_language_heatmap-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/MultiAIComparison.json b/grafana/dashboards/postgresql/multi-ai-comparison.json similarity index 77% rename from grafana/dashboards/postgresql/MultiAIComparison.json rename to grafana/dashboards/postgresql/multi-ai-comparison.json index bb0bd28b023..98ae5d766e0 100644 --- a/grafana/dashboards/postgresql/MultiAIComparison.json +++ b/grafana/dashboards/postgresql/multi-ai-comparison.json @@ -134,14 +134,14 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT user_id) AS \"Kiro Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT user_id) AS \"Kiro Active Users\" FROM _tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" }, { "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, MAX(daily_active_users) AS \"Copilot Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS time, MAX(daily_active_users) AS \"Copilot Active Users\" FROM _tool_copilot_enterprise_daily_metrics WHERE ('${copilot_connection_id}' = '' OR connection_id::text = '${copilot_connection_id}') AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "B" } ], @@ -217,7 +217,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_suggestions_count) AS \"Kiro: Suggestions\", SUM(inline_acceptance_count) AS \"Kiro: Accepted\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(inline_suggestions_count) AS \"Kiro: Suggestions\", SUM(inline_acceptance_count) AS \"Kiro: Accepted\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -280,7 +280,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, SUM(code_generation_activity_count) AS \"Copilot: Suggestions\", SUM(code_acceptance_activity_count) AS \"Copilot: Accepted\" FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(code_generation_activity_count) AS \"Copilot: Suggestions\", SUM(code_acceptance_activity_count) AS \"Copilot: Accepted\" FROM _tool_copilot_enterprise_daily_metrics WHERE ('${copilot_connection_id}' = '' OR connection_id::text = '${copilot_connection_id}') AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -356,7 +356,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT time, SUM(kiro_loc) AS \"Kiro LOC Accepted\", SUM(copilot_loc) AS \"Copilot LOC Added\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' AS time, SUM(inline_ai_code_lines + chat_ai_code_lines) AS kiro_loc, 0 AS copilot_loc FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM date) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day' AS time, 0 AS kiro_loc, SUM(loc_added_sum) AS copilot_loc FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM day) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "SELECT time, SUM(kiro_loc) AS \"Kiro LOC Accepted\", SUM(copilot_loc) AS \"Copilot LOC Added\" FROM (SELECT CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' AS time, SUM(inline_ai_code_lines + chat_ai_code_lines) AS kiro_loc, 0 AS copilot_loc FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY CAST(date AS DATE) - (EXTRACT(ISODOW FROM CAST(date AS DATE)) - 1) * INTERVAL '1 day' UNION ALL SELECT CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS time, 0 AS kiro_loc, SUM(loc_added_sum) AS copilot_loc FROM _tool_copilot_enterprise_daily_metrics WHERE ('${copilot_connection_id}' = '' OR connection_id::text = '${copilot_connection_id}') AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day) GROUP BY CAST(day AS DATE) - (EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day') AS combined GROUP BY time ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -407,7 +407,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'Kiro' AS Tool, ROUND(CAST(SUM(inline_acceptance_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0), 1) AS \"Acceptance Rate\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Copilot' AS Tool, ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) FROM _tool_copilot_enterprise_daily_metrics WHERE connection_id = ${copilot_connection_id} AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day)", + "rawSql": "SELECT 'Kiro' AS \"Tool\", ROUND(CAST(SUM(inline_acceptance_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0), 1) AS \"Acceptance Rate\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Copilot' AS \"Tool\", ROUND(CAST(SUM(code_acceptance_activity_count) * 100.0 AS NUMERIC) / NULLIF(NULLIF(SUM(code_generation_activity_count), 0), 0), 1) FROM _tool_copilot_enterprise_daily_metrics WHERE ('${copilot_connection_id}' = '' OR connection_id::text = '${copilot_connection_id}') AND scope_id = '${copilot_scope_id}' AND $__timeFilter(day)", "refId": "A" } ], @@ -429,24 +429,24 @@ { "current": {}, "datasource": "postgresql", - "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "definition": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC NULLS LAST", "hide": 0, "label": "Copilot Connection", "name": "copilot_connection_id", "options": [], - "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC", + "query": "SELECT DISTINCT connection_id FROM _tool_copilot_scopes ORDER BY connection_id DESC NULLS LAST", "refresh": 1, "type": "query" }, { "current": {}, "datasource": "postgresql", - "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS UNSIGNED)", + "definition": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS BIGINT)", "hide": 0, "label": "Copilot Scope", "name": "copilot_scope_id", "options": [], - "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS UNSIGNED)", + "query": "SELECT DISTINCT id FROM _tool_copilot_scopes WHERE connection_id = CAST('${copilot_connection_id}' AS BIGINT)", "refresh": 1, "type": "query" } @@ -458,7 +458,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Multi-AI Tool Comparison (PostgreSQL)", + "title": "Multi-AI Tool Comparison", "uid": "multi_ai_comparison-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/PagerDuty.json b/grafana/dashboards/postgresql/pager-duty.json similarity index 90% rename from grafana/dashboards/postgresql/PagerDuty.json rename to grafana/dashboards/postgresql/pager-duty.json index f8120b952b8..751629fbc6c 100644 --- a/grafana/dashboards/postgresql/PagerDuty.json +++ b/grafana/dashboards/postgresql/pager-duty.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -398,7 +398,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT b.name AS service, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, ROUND((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)), 1) AS lead_time_days, i.url FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT b.name AS service, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS lead_time_days, i.url FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -505,7 +505,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -639,7 +639,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -773,7 +773,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -882,7 +882,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[])) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -1021,7 +1021,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1123,7 +1123,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id = ANY(ARRAY[${board_id}]::text[]) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(CONCAT(lead_time_day, 'd'), 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1266,14 +1266,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from boards where id like 'pagerduty%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'pagerduty%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "select concat(name, '--', id) from boards where id like 'pagerduty%'", + "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'pagerduty%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1288,7 +1288,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "PagerDuty (PostgreSQL)", + "title": "PagerDuty", "uid": "abb26d07-3268-4bdf-b871-6e39b37b9e00-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/QDevDORA.json b/grafana/dashboards/postgresql/q-dev-dora.json similarity index 76% rename from grafana/dashboards/postgresql/QDevDORA.json rename to grafana/dashboards/postgresql/q-dev-dora.json index 1462596bb28..b7f82438a6c 100644 --- a/grafana/dashboards/postgresql/QDevDORA.json +++ b/grafana/dashboards/postgresql/q-dev-dora.json @@ -316,7 +316,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS \"Deployments\" FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", + "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS \"Deployments\" FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", "refId": "A" } ], @@ -383,7 +383,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0), 1) AS \"Lead Time (hours)\" FROM _median_ranks WHERE ranks <= 0.5", + "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) AS \"Lead Time (hours)\" FROM _median_ranks WHERE ranks <= 0.5", "refId": "A" } ], @@ -450,7 +450,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS \"Change Failure Rate\" FROM _failure_caused", + "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS \"Change Failure Rate\" FROM _failure_caused", "refId": "A" } ], @@ -601,7 +601,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), lead_time_monthly AS (SELECT TO_CHAR(cdc.finished_date, '%Y-%m-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS \"AI Accepted Lines\", ROUND(lt.avg_lead_time, 1) AS \"Median Lead Time (hours)\" FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), lead_time_monthly AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS \"AI Accepted Lines\", ROUND(lt.avg_lead_time, 1) AS \"Median Lead Time (hours)\" FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -743,7 +743,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), deployment_count AS (SELECT TO_CHAR(MAX(cdc.finished_date), '%Y-%m-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS \"AI Acceptance Rate\", dc.deploy_count AS \"Deployment Count\" FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), deployment_count AS (SELECT TO_CHAR(CAST(MAX(cdc.finished_date) AS TIMESTAMP), 'YYYY-MM-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS \"AI Acceptance Rate\", dc.deploy_count AS \"Deployment Count\" FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -885,7 +885,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(date, '%Y-%m-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01')), cfr_monthly AS (SELECT TO_CHAR(deployment_finished_date, '%Y-%m-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, '%Y-%m-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS \"AI Generated Tests\", cfr.cfr AS \"Change Failure Rate\" FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), cfr_monthly AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS \"AI Generated Tests\", cfr.cfr AS \"Change Failure Rate\" FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1023,7 +1023,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT TO_DATE(TO_CHAR(date, '%Y-%m-01'), '%Y-%m-%d') AS time, COUNT(DISTINCT user_id) AS \"Active Users\", SUM(code_review_findings_count) AS \"Code Review Findings\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, '%Y-%m-01') ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01'), '%Y-%m-%d') AS time, COUNT(DISTINCT user_id) AS \"Active Users\", SUM(code_review_findings_count) AS \"Code Review Findings\" FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1175,7 +1175,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(date, 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(date, 'YYYY-MM')), dora_metrics AS (SELECT TO_CHAR(cdc.finished_date, 'YYYY-MM') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(cdc.finished_date, 'YYYY-MM')), cfr_metrics AS (SELECT TO_CHAR(deployment_finished_date, 'YYYY-MM') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name = ANY(ARRAY[${project}]::text[]) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING $__timeFilter(MAX(cdc.finished_date))) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(deployment_finished_date, 'YYYY-MM')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS \"Month\", COALESCE(ai.active_users, 0) AS \"Q Dev Users\", COALESCE(ai.ai_lines, 0) AS \"AI Code Lines\", ai.acceptance_rate AS \"AI Acceptance Rate\", COALESCE(ai.generated_tests, 0) AS \"AI Tests\", COALESCE(ai.review_findings, 0) AS \"Review Findings\", COALESCE(dm.deployments, 0) AS \"Deployments\", ROUND(dm.avg_lead_time, 1) AS \"Lead Time (hours)\", cfr.cfr AS \"Change Failure Rate\" FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", + "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM')), dora_metrics AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM')), cfr_metrics AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS \"Month\", COALESCE(ai.active_users, 0) AS \"Q Dev Users\", COALESCE(ai.ai_lines, 0) AS \"AI Code Lines\", ai.acceptance_rate AS \"AI Acceptance Rate\", COALESCE(ai.generated_tests, 0) AS \"AI Tests\", COALESCE(ai.review_findings, 0) AS \"Review Findings\", COALESCE(dm.deployments, 0) AS \"Deployments\", ROUND(dm.avg_lead_time, 1) AS \"Lead Time (hours)\", cfr.cfr AS \"Change Failure Rate\" FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", "refId": "A" } ], @@ -1227,7 +1227,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Q Dev + DORA Correlation (PostgreSQL)", + "title": "Q Dev + DORA Correlation", "uid": "qdev_dora_correlation-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_executive.json b/grafana/dashboards/postgresql/qdev_executive.json index 1311ecd3a09..d87f67c5aba 100644 --- a/grafana/dashboards/postgresql/qdev_executive.json +++ b/grafana/dashboards/postgresql/qdev_executive.json @@ -179,7 +179,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(SUM(d.total_accepted), 0), 0), 2) AS \"Credits per Accepted Line\" FROM (SELECT user_id, date, SUM(credits_used) AS credits_used FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r JOIN (SELECT user_id, date, (inline_ai_code_lines + chat_ai_code_lines + code_fix_accepted_lines + dev_accepted_lines) AS total_accepted FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)) AS d ON r.user_id = d.user_id AND r.date = d.date", + "rawSql": "SELECT ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(NULLIF(SUM(d.total_accepted), 0), 0), 2) AS \"Credits per Accepted Line\" FROM (SELECT user_id, date, SUM(credits_used) AS credits_used FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY \"user_id\", date) AS r JOIN (SELECT user_id, \"date\", (inline_ai_code_lines + chat_ai_code_lines + code_fix_accepted_lines + dev_accepted_lines) AS total_accepted FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)) AS d ON r.user_id = d.user_id AND r.date = d.date", "refId": "A" } ], @@ -298,7 +298,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100, 0) AS \"Steering %\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) * 100 AS DECIMAL), 0) AS \"Steering %\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -403,7 +403,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, COUNT(DISTINCT user_id) AS \"Active Users\" FROM (SELECT user_id, YEARWEEK(timestamp, 1) AS yw FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY yw ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(yw || ' Monday', 'IYYYIW FMDay') AS time, COUNT(DISTINCT user_id) AS \"Active Users\" FROM (SELECT user_id, (EXTRACT(ISOYEAR FROM timestamp) * 100 + EXTRACT(WEEK FROM timestamp))::int AS yw FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)) AS t GROUP BY \"yw\" ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -495,7 +495,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT TO_DATE(CONCAT(yw, ' Monday'), '%X%V %W') AS time, SUM(CASE WHEN yw = first_yw THEN 1 ELSE 0 END) AS \"New Users\", SUM(CASE WHEN yw <> first_yw THEN 1 ELSE 0 END) AS \"Returning Users\" FROM (SELECT DISTINCT u.user_id, YEARWEEK(u.timestamp, 1) AS yw, f.first_yw FROM lake._tool_q_dev_chat_log AS u JOIN (SELECT user_id, YEARWEEK(MIN(timestamp), 1) AS first_yw FROM lake._tool_q_dev_chat_log GROUP BY user_id) AS f ON u.user_id = f.user_id WHERE $__timeFilter(u.timestamp)) AS weekly GROUP BY yw ORDER BY time NULLS FIRST", + "rawSql": "SELECT TO_DATE(yw || ' Monday', 'IYYYIW FMDay') AS time, SUM(CASE WHEN yw = first_yw THEN 1 ELSE 0 END) AS \"New Users\", SUM(CASE WHEN yw <> first_yw THEN 1 ELSE 0 END) AS \"Returning Users\" FROM (SELECT DISTINCT u.user_id, (EXTRACT(ISOYEAR FROM u.timestamp) * 100 + EXTRACT(WEEK FROM u.timestamp))::int AS yw, f.first_yw FROM lake._tool_q_dev_chat_log AS u JOIN (SELECT user_id, (EXTRACT(ISOYEAR FROM MIN(timestamp)) * 100 + EXTRACT(WEEK FROM MIN(timestamp)))::int AS first_yw FROM lake._tool_q_dev_chat_log GROUP BY user_id) AS f ON u.user_id = f.user_id WHERE $__timeFilter(u.timestamp)) AS weekly GROUP BY yw ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -600,7 +600,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER BY date NULLS FIRST) AS \"Cumulative Credits\", (SELECT CAST(SUM(credits_used) AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * DAY(CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE) AS DATE)) FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE))) AS \"Projected Monthly\" FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE)) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(SUM(credits_used)) OVER (ORDER BY date NULLS FIRST) AS \"Cumulative Credits\", (SELECT CAST(SUM(credits_used) AS NUMERIC) / NULLIF(COUNT(DISTINCT date), 0) * EXTRACT(DAY FROM CAST(CAST(DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE) AS DATE)) FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE))) AS \"Projected Monthly\" FROM lake._tool_q_dev_user_report WHERE YEAR(CAST(date AS DATE)) = YEAR(CAST(CURRENT_DATE AS DATE)) AND MONTH(CAST(date AS DATE)) = MONTH(CAST(CURRENT_DATE AS DATE)) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -662,7 +662,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", MAX(subscription_tier) AS \"Tier\", ROUND(SUM(credits_used), 1) AS \"Total Credits Used\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY user_id HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", MAX(subscription_tier) AS \"Tier\", ROUND(SUM(credits_used), 1) AS \"Total Credits Used\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND subscription_tier = 'POWER' GROUP BY \"user_id\" HAVING MAX(date) < NOW() - INTERVAL '14 DAY' ORDER BY MAX(date) NULLS FIRST", "refId": "A" } ], @@ -737,7 +737,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(d.display_name), d.user_id) AS \"User\", COALESCE(MAX(r.subscription_tier), '') AS \"Tier\", ROUND(SUM(r.credits_used), 1) AS \"Credits Used\", SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) AS \"Total Accepted Lines\", CASE WHEN SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) > 0 THEN ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines), 0), 2) ELSE NULL END AS \"Credits/Line\", CONCAT(ROUND(CAST(SUM(d.inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(d.inline_suggestions_count), 0), 0) * 100, 1), '%') AS \"Accept Rate\", SUM(d.code_review_findings_count) AS \"Review Findings\", SUM(d.test_generation_event_count) AS \"Test Gen Events\", SUM(d.dev_accepted_lines) AS \"Agentic Lines\", MIN(d.date) AS \"First Active\", MAX(d.date) AS \"Last Active\" FROM lake._tool_q_dev_user_data AS d LEFT JOIN (SELECT user_id, date, SUM(credits_used) AS credits_used, MAX(subscription_tier) AS subscription_tier FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, date) AS r ON d.user_id = r.user_id AND d.date = r.date WHERE $__timeFilter(d.date) GROUP BY d.user_id ORDER BY SUM(r.credits_used) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(d.display_name), d.user_id) AS \"User\", COALESCE(MAX(r.subscription_tier), '') AS \"Tier\", ROUND(SUM(r.credits_used), 1) AS \"Credits Used\", SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) AS \"Total Accepted Lines\", CASE WHEN SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines) > 0 THEN ROUND(CAST(SUM(r.credits_used) AS NUMERIC) / NULLIF(SUM(d.chat_ai_code_lines + d.inline_ai_code_lines + d.code_fix_accepted_lines + d.dev_accepted_lines), 0), 2) ELSE NULL END AS \"Credits/Line\", ROUND(CAST(SUM(d.inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(d.inline_suggestions_count), 0), 0) * 100, 1) || '%' AS \"Accept Rate\", SUM(d.code_review_findings_count) AS \"Review Findings\", SUM(d.test_generation_event_count) AS \"Test Gen Events\", SUM(d.dev_accepted_lines) AS \"Agentic Lines\", MIN(d.date) AS \"First Active\", MAX(d.date) AS \"Last Active\" FROM lake._tool_q_dev_user_data AS d LEFT JOIN (SELECT user_id, date, SUM(credits_used) AS credits_used, MAX(subscription_tier) AS subscription_tier FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY \"user_id\", date) AS r ON d.user_id = r.user_id AND d.date = r.date WHERE $__timeFilter(d.date) GROUP BY d.user_id ORDER BY SUM(r.credits_used) DESC NULLS LAST", "refId": "A" } ], @@ -762,7 +762,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Executive Dashboard (PostgreSQL)", + "title": "Kiro Executive Dashboard", "uid": "qdev_executive-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_feature_metrics.json b/grafana/dashboards/postgresql/qdev_feature_metrics.json index 181532e6781..808fbf57eb0 100644 --- a/grafana/dashboards/postgresql/qdev_feature_metrics.json +++ b/grafana/dashboards/postgresql/qdev_feature_metrics.json @@ -174,7 +174,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", SUM(inline_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", SUM(inline_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -265,7 +265,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -369,7 +369,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Messages Sent\", SUM(chat_messages_interacted) AS \"Messages Interacted\", SUM(chat_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Messages Sent\", SUM(chat_messages_interacted) AS \"Messages Interacted\", SUM(chat_ai_code_lines) AS \"AI Code Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -460,7 +460,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Generation Events\", SUM(dev_generated_lines) AS \"Generated Lines\", SUM(dev_accepted_lines) AS \"Accepted Lines\", SUM(dev_acceptance_event_count) AS \"Acceptance Events\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Generation Events\", SUM(dev_generated_lines) AS \"Generated Lines\", SUM(dev_accepted_lines) AS \"Accepted Lines\", SUM(dev_acceptance_event_count) AS \"Acceptance Events\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -564,7 +564,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(code_review_findings_count) AS \"Review Findings\", SUM(code_review_succeeded_event_count) AS \"Reviews Succeeded\", SUM(code_review_failed_event_count) AS \"Reviews Failed\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(code_review_findings_count) AS \"Review Findings\", SUM(code_review_succeeded_event_count) AS \"Reviews Succeeded\", SUM(code_review_failed_event_count) AS \"Reviews Failed\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -655,7 +655,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(test_generation_generated_tests) AS \"Tests Generated\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(test_generation_generated_lines) AS \"Lines Generated\", SUM(test_generation_accepted_lines) AS \"Lines Accepted\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(test_generation_generated_tests) AS \"Tests Generated\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(test_generation_generated_lines) AS \"Lines Generated\", SUM(test_generation_accepted_lines) AS \"Lines Accepted\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -746,7 +746,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(doc_generation_accepted_line_additions) AS \"Doc Lines Accepted\", SUM(transformation_event_count) AS \"Transformation Events\", SUM(transformation_lines_generated) AS \"Transform Lines Generated\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(doc_generation_accepted_line_additions) AS \"Doc Lines Accepted\", SUM(transformation_event_count) AS \"Transformation Events\", SUM(transformation_lines_generated) AS \"Transform Lines Generated\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -842,7 +842,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT 'Chat' AS Feature, COUNT(DISTINCT CASE WHEN chat_messages_sent > 0 THEN user_id END) AS Users FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Inline Suggestions', COUNT(DISTINCT CASE WHEN inline_suggestions_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Fix', COUNT(DISTINCT CASE WHEN code_fix_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Review', COUNT(DISTINCT CASE WHEN code_review_succeeded_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Doc Generation', COUNT(DISTINCT CASE WHEN doc_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Test Generation', COUNT(DISTINCT CASE WHEN test_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Dev (Agentic)', COUNT(DISTINCT CASE WHEN dev_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Transformation', COUNT(DISTINCT CASE WHEN transformation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", + "rawSql": "SELECT 'Chat' AS \"Feature\", COUNT(DISTINCT CASE WHEN chat_messages_sent > 0 THEN user_id END) AS \"Users\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Inline Suggestions', COUNT(DISTINCT CASE WHEN inline_suggestions_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Fix', COUNT(DISTINCT CASE WHEN code_fix_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Code Review', COUNT(DISTINCT CASE WHEN code_review_succeeded_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Doc Generation', COUNT(DISTINCT CASE WHEN doc_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Test Generation', COUNT(DISTINCT CASE WHEN test_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Dev (Agentic)', COUNT(DISTINCT CASE WHEN dev_generation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) UNION ALL SELECT 'Transformation', COUNT(DISTINCT CASE WHEN transformation_event_count > 0 THEN user_id END) FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date)", "refId": "A" } ], @@ -917,7 +917,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1), '%') AS \"Accept %\", SUM(chat_messages_sent) AS \"Chat Msgs\", SUM(chat_ai_code_lines) AS \"Chat Lines\", SUM(dev_accepted_lines) AS \"Agentic Lines\", SUM(code_review_findings_count) AS \"Review Findings\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(doc_generation_event_count) AS \"Doc Gen\", SUM(transformation_event_count) AS \"Transforms\", MIN(date) AS \"First Active\", MAX(date) AS \"Last Active\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_suggestions_count) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(inline_suggestions_count) AS \"Suggestions\", SUM(inline_acceptance_count) AS \"Accepted\", ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 1) || '%' AS \"Accept %\", SUM(chat_messages_sent) AS \"Chat Msgs\", SUM(chat_ai_code_lines) AS \"Chat Lines\", SUM(dev_accepted_lines) AS \"Agentic Lines\", SUM(code_review_findings_count) AS \"Review Findings\", SUM(test_generation_accepted_tests) AS \"Tests Accepted\", SUM(doc_generation_event_count) AS \"Doc Gen\", SUM(transformation_event_count) AS \"Transforms\", MIN(date) AS \"First Active\", MAX(date) AS \"Last Active\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"user_id\" ORDER BY SUM(inline_suggestions_count) DESC NULLS LAST", "refId": "A" } ], @@ -942,7 +942,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Legacy Feature Metrics (PostgreSQL)", + "title": "Kiro Legacy Feature Metrics", "uid": "qdev_feature_metrics-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_logging.json b/grafana/dashboards/postgresql/qdev_logging.json index 8d06a52b7f2..7e502ca07a3 100644 --- a/grafana/dashboards/postgresql/qdev_logging.json +++ b/grafana/dashboards/postgresql/qdev_logging.json @@ -227,7 +227,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS \"Trigger Type\", COUNT(*) AS \"Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY chat_trigger_type ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN chat_trigger_type = '' OR chat_trigger_type IS NULL THEN '(unknown)' ELSE chat_trigger_type END AS \"Trigger Type\", COUNT(*) AS \"Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY \"chat_trigger_type\" ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -295,7 +295,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS \"Model\", COUNT(*) AS \"Requests\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY model_id ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN model_id = '' OR model_id IS NULL THEN '(unknown)' ELSE model_id END AS \"Model\", COUNT(*) AS \"Requests\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY \"model_id\" ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -363,7 +363,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"File Type\", COUNT(*) AS \"Completions\" FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "rawSql": "SELECT CASE WHEN file_extension = '' THEN '(unknown)' ELSE file_extension END AS \"File Type\", COUNT(*) AS \"Completions\" FROM lake._tool_q_dev_completion_log WHERE $__timeFilter(timestamp) GROUP BY \"file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", "refId": "A" } ], @@ -745,7 +745,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file active)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY active_file_extension ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", + "rawSql": "SELECT CASE WHEN active_file_extension = '' OR active_file_extension IS NULL THEN '(no file active)' ELSE active_file_extension END AS \"File Type\", COUNT(*) AS \"Chat Events\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY \"active_file_extension\" ORDER BY COUNT(*) DESC NULLS LAST LIMIT 15", "refId": "A" } ], @@ -813,7 +813,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(CASE WHEN code_reference_count > 0 THEN 1 ELSE 0 END) AS \"With Code References\", SUM(CASE WHEN web_link_count > 0 THEN 1 ELSE 0 END) AS \"With Web Links\", SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS \"With Followup Prompts\", SUM(CASE WHEN code_reference_count = 0 AND web_link_count = 0 AND has_followup_prompts = FALSE THEN 1 ELSE 0 END) AS \"Plain Response\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT SUM(CASE WHEN code_reference_count > 0 THEN 1 ELSE 0 END) AS \"With Code References\", SUM(CASE WHEN web_link_count > 0 THEN 1 ELSE 0 END) AS \"With Web Links\", SUM(CASE WHEN has_followup_prompts = TRUE THEN 1 ELSE 0 END) AS \"With Followup Prompts\", SUM(CASE WHEN code_reference_count = '0' AND web_link_count = '0' AND has_followup_prompts = FALSE THEN 1 ELSE 0 END) AS \"Plain Response\" FROM lake._tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -1112,7 +1112,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro AI Activity Insights (PostgreSQL)", + "title": "Kiro AI Activity Insights", "uid": "qdev_logging-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_user_data.json b/grafana/dashboards/postgresql/qdev_user_data.json index a8f83d8a833..2d07290eccb 100644 --- a/grafana/dashboards/postgresql/qdev_user_data.json +++ b/grafana/dashboards/postgresql/qdev_user_data.json @@ -201,7 +201,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_ai_code_lines) AS \"Chat Accepted Lines\", SUM(code_fix_accepted_lines) AS \"Code Fix Accepted Lines\", SUM(code_fix_generated_lines) AS \"Code Fix Generated Lines\", SUM(transformation_lines_ingested) AS \"Java Transform Ingested Lines\", SUM(transformation_lines_generated) AS \"Java Transform Generated Lines\", SUM(inline_ai_code_lines) AS \"Inline Suggestion Accepted Lines\", SUM(inline_chat_accepted_line_additions) AS \"Inline Chat Accepted Line Additions\", SUM(inline_chat_accepted_line_deletions) AS \"Inline Chat Accepted Line Deletions\", SUM(inline_chat_dismissed_line_additions) AS \"Inline Chat Dismissed Line Additions\", SUM(inline_chat_dismissed_line_deletions) AS \"Inline Chat Dismissed Line Deletions\", SUM(inline_chat_rejected_line_additions) AS \"Inline Chat Rejected Line Additions\", SUM(inline_chat_rejected_line_deletions) AS \"Inline Chat Rejected Line Deletions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_ai_code_lines) AS \"Chat Accepted Lines\", SUM(code_fix_accepted_lines) AS \"Code Fix Accepted Lines\", SUM(code_fix_generated_lines) AS \"Code Fix Generated Lines\", SUM(transformation_lines_ingested) AS \"Java Transform Ingested Lines\", SUM(transformation_lines_generated) AS \"Java Transform Generated Lines\", SUM(inline_ai_code_lines) AS \"Inline Suggestion Accepted Lines\", SUM(inline_chat_accepted_line_additions) AS \"Inline Chat Accepted Line Additions\", SUM(inline_chat_accepted_line_deletions) AS \"Inline Chat Accepted Line Deletions\", SUM(inline_chat_dismissed_line_additions) AS \"Inline Chat Dismissed Line Additions\", SUM(inline_chat_dismissed_line_deletions) AS \"Inline Chat Dismissed Line Deletions\", SUM(inline_chat_rejected_line_additions) AS \"Inline Chat Rejected Line Additions\", SUM(inline_chat_rejected_line_deletions) AS \"Inline Chat Rejected Line Deletions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -330,7 +330,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Chat Messages Sent\", SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(transformation_event_count) AS \"Java Transform Event Count\", SUM(inline_acceptance_count) AS \"Inline Suggestion Accepted Suggestions\", SUM(inline_suggestions_count) AS \"Inline Suggestion Count\", SUM(inline_chat_total_event_count) AS \"Inline Chat Total Suggestions\", SUM(inline_chat_acceptance_event_count) AS \"Inline Chat Accepted Suggestions\", SUM(inline_chat_dismissal_event_count) AS \"Inline Chat Dismissed Suggestions\", SUM(inline_chat_rejection_event_count) AS \"Inline Chat Rejected Suggestions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(chat_messages_sent) AS \"Chat Messages Sent\", SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(transformation_event_count) AS \"Java Transform Event Count\", SUM(inline_acceptance_count) AS \"Inline Suggestion Accepted Suggestions\", SUM(inline_suggestions_count) AS \"Inline Suggestion Count\", SUM(inline_chat_total_event_count) AS \"Inline Chat Total Suggestions\", SUM(inline_chat_acceptance_event_count) AS \"Inline Chat Accepted Suggestions\", SUM(inline_chat_dismissal_event_count) AS \"Inline Chat Dismissed Suggestions\", SUM(inline_chat_rejection_event_count) AS \"Inline Chat Rejected Suggestions\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -458,7 +458,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(code_review_findings_count) AS \"Total Findings\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(code_fix_acceptance_event_count) AS \"Code Fix Accepted Event Count\", SUM(code_fix_generation_event_count) AS \"Code Fix Generated Event Count\", SUM(code_review_findings_count) AS \"Total Findings\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -587,7 +587,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) AS \"Code Fix\", CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS \"Inline Suggestions\", CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) AS \"Inline Chat\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -730,7 +730,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(chat_ai_code_lines) AS \"Accepted Lines (Chat)\", SUM(transformation_lines_ingested) AS \"Lines Ingested (Java Transform)\", SUM(transformation_lines_generated) AS \"Lines Generated (Java Transform)\", SUM(transformation_event_count) AS \"Event Count (Java Transform)\", SUM(code_review_findings_count) AS \"Findings (Code Review)\", SUM(code_fix_accepted_lines) AS \"Accepted Lines (Code Fix)\", SUM(code_fix_generated_lines) AS \"Generated Lines (Code Fix)\", SUM(code_fix_acceptance_event_count) AS \"Accepted Count (Code Fix)\", SUM(code_fix_generation_event_count) AS \"Generated Count (Code Fix)\", CONCAT(ROUND(CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Code Fix)\", SUM(inline_ai_code_lines) AS \"Accepted Lines (Inline Suggestion)\", SUM(inline_acceptance_count) AS \"Accepted Count (Inline Suggestion)\", SUM(inline_suggestions_count) AS \"Total Count (Inline Suggestion)\", CONCAT(ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Inline Suggestion)\", SUM(inline_chat_accepted_line_additions) AS \"Accepted Line Additions (Inline Chat)\", SUM(inline_chat_accepted_line_deletions) AS \"Accepted Line Deletions (Inline Chat)\", SUM(inline_chat_acceptance_event_count) AS \"Accepted Events (Inline Chat)\", SUM(inline_chat_total_event_count) AS \"Total Events (Inline Chat)\", CONCAT(ROUND(CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) * 100, 2), '%') AS \"Acceptance Rate (Inline Chat)\", SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY user_id ORDER BY SUM(inline_ai_code_lines) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", SUM(chat_ai_code_lines) AS \"Accepted Lines (Chat)\", SUM(transformation_lines_ingested) AS \"Lines Ingested (Java Transform)\", SUM(transformation_lines_generated) AS \"Lines Generated (Java Transform)\", SUM(transformation_event_count) AS \"Event Count (Java Transform)\", SUM(code_review_findings_count) AS \"Findings (Code Review)\", SUM(code_fix_accepted_lines) AS \"Accepted Lines (Code Fix)\", SUM(code_fix_generated_lines) AS \"Generated Lines (Code Fix)\", SUM(code_fix_acceptance_event_count) AS \"Accepted Count (Code Fix)\", SUM(code_fix_generation_event_count) AS \"Generated Count (Code Fix)\", ROUND(CAST(SUM(code_fix_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(code_fix_generation_event_count), 0), 0) * 100, 2) || '%' AS \"Acceptance Rate (Code Fix)\", SUM(inline_ai_code_lines) AS \"Accepted Lines (Inline Suggestion)\", SUM(inline_acceptance_count) AS \"Accepted Count (Inline Suggestion)\", SUM(inline_suggestions_count) AS \"Total Count (Inline Suggestion)\", ROUND(CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) * 100, 2) || '%' AS \"Acceptance Rate (Inline Suggestion)\", SUM(inline_chat_accepted_line_additions) AS \"Accepted Line Additions (Inline Chat)\", SUM(inline_chat_accepted_line_deletions) AS \"Accepted Line Deletions (Inline Chat)\", SUM(inline_chat_acceptance_event_count) AS \"Accepted Events (Inline Chat)\", SUM(inline_chat_total_event_count) AS \"Total Events (Inline Chat)\", ROUND(CAST(SUM(inline_chat_acceptance_event_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_chat_total_event_count), 0), 0) * 100, 2) || '%' AS \"Acceptance Rate (Inline Chat)\", SUM(doc_generation_event_count) AS \"Doc Gen Events\", SUM(test_generation_event_count) AS \"Test Gen Events\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"user_id\" ORDER BY SUM(inline_ai_code_lines) DESC NULLS LAST", "refId": "A", "select": [ [ @@ -859,7 +859,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Generation Events\", SUM(doc_generation_accepted_line_additions) AS \"Accepted Line Additions\", SUM(doc_generation_accepted_line_updates) AS \"Accepted Line Updates\", SUM(doc_generation_rejected_line_additions) AS \"Rejected Line Additions\", SUM(doc_generation_rejected_line_updates) AS \"Rejected Line Updates\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(doc_generation_event_count) AS \"Doc Generation Events\", SUM(doc_generation_accepted_line_additions) AS \"Accepted Line Additions\", SUM(doc_generation_accepted_line_updates) AS \"Accepted Line Updates\", SUM(doc_generation_rejected_line_additions) AS \"Rejected Line Additions\", SUM(doc_generation_rejected_line_updates) AS \"Rejected Line Updates\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -988,7 +988,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Generation Events\", SUM(test_generation_accepted_tests) AS \"Accepted Tests\", SUM(test_generation_generated_tests) AS \"Generated Tests\", SUM(test_generation_accepted_lines) AS \"Accepted Lines\", SUM(test_generation_generated_lines) AS \"Generated Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(test_generation_event_count) AS \"Test Generation Events\", SUM(test_generation_accepted_tests) AS \"Accepted Tests\", SUM(test_generation_generated_tests) AS \"Generated Tests\", SUM(test_generation_accepted_lines) AS \"Accepted Lines\", SUM(test_generation_generated_lines) AS \"Generated Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -1117,7 +1117,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Dev Generation Events\", SUM(dev_acceptance_event_count) AS \"Dev Acceptance Events\", SUM(dev_generated_lines) AS \"Dev Generated Lines\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(dev_generation_event_count) AS \"Dev Generation Events\", SUM(dev_acceptance_event_count) AS \"Dev Acceptance Events\", SUM(dev_generated_lines) AS \"Dev Generated Lines\", SUM(dev_accepted_lines) AS \"Dev Accepted Lines\" FROM lake._tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A", "select": [ [ @@ -1176,7 +1176,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Code Metrics Dashboard (PostgreSQL)", + "title": "Kiro Code Metrics Dashboard", "uid": "qdev_user_data-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/qdev_user_report.json b/grafana/dashboards/postgresql/qdev_user_report.json index 65111016a1e..e678e9339e8 100644 --- a/grafana/dashboards/postgresql/qdev_user_report.json +++ b/grafana/dashboards/postgresql/qdev_user_report.json @@ -162,7 +162,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, subscription_tier AS metric, SUM(credits_used) AS value FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date, subscription_tier ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, subscription_tier AS metric, SUM(credits_used) AS value FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY \"date\", \"subscription_tier\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -254,7 +254,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT date AS time, SUM(CASE WHEN client_type = 'KIRO_IDE' THEN total_messages ELSE 0 END) AS \"Messages (IDE)\", SUM(CASE WHEN client_type = 'KIRO_CLI' THEN total_messages ELSE 0 END) AS \"Messages (CLI)\", SUM(CASE WHEN client_type = 'PLUGIN' THEN total_messages ELSE 0 END) AS \"Messages (Plugin)\", SUM(chat_conversations) AS \"Conversations\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY date ORDER BY date NULLS FIRST", + "rawSql": "SELECT date AS time, SUM(CASE WHEN client_type = 'KIRO_IDE' THEN total_messages ELSE 0 END) AS \"Messages (IDE)\", SUM(CASE WHEN client_type = 'KIRO_CLI' THEN total_messages ELSE 0 END) AS \"Messages (CLI)\", SUM(CASE WHEN client_type = 'PLUGIN' THEN total_messages ELSE 0 END) AS \"Messages (Plugin)\", SUM(chat_conversations) AS \"Conversations\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY \"date\" ORDER BY date NULLS FIRST", "refId": "A" } ], @@ -322,7 +322,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT subscription_tier AS \"Tier\", COUNT(DISTINCT user_id) AS \"Users\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY subscription_tier ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST", + "rawSql": "SELECT subscription_tier AS \"Tier\", COUNT(DISTINCT user_id) AS \"Users\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) AND NOT subscription_tier IS NULL AND subscription_tier <> '' GROUP BY \"subscription_tier\" ORDER BY COUNT(DISTINCT user_id) DESC NULLS LAST", "refId": "A" } ], @@ -433,7 +433,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", subscription_tier AS \"Tier\", client_type AS \"Client\", SUM(credits_used) AS \"Credits Used\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\", SUM(overage_credits_used) AS \"Overage Credits\", CASE WHEN MAX(CAST(overage_enabled AS UBIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS \"Overage\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY user_id, subscription_tier, client_type ORDER BY user_id DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", subscription_tier AS \"Tier\", client_type AS \"Client\", SUM(credits_used) AS \"Credits Used\", SUM(total_messages) AS \"Messages\", SUM(chat_conversations) AS \"Conversations\", SUM(overage_credits_used) AS \"Overage Credits\", CASE WHEN MAX(CAST(overage_enabled AS BIGINT)) = 1 THEN 'Yes' ELSE 'No' END AS \"Overage\", MIN(date) AS \"First Activity\", MAX(date) AS \"Last Activity\" FROM lake._tool_q_dev_user_report WHERE $__timeFilter(date) GROUP BY \"user_id\", \"subscription_tier\", \"client_type\" ORDER BY user_id DESC NULLS LAST", "refId": "A" } ], @@ -458,7 +458,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Usage Dashboard (PostgreSQL)", + "title": "Kiro Usage Dashboard", "uid": "qdev_user_report-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/SonarQubeCloud.json b/grafana/dashboards/postgresql/sonar-qube-cloud.json similarity index 87% rename from grafana/dashboards/postgresql/SonarQubeCloud.json rename to grafana/dashboards/postgresql/sonar-qube-cloud.json index 5d1f8502a7e..ff5b9547eef 100644 --- a/grafana/dashboards/postgresql/SonarQubeCloud.json +++ b/grafana/dashboards/postgresql/sonar-qube-cloud.json @@ -152,7 +152,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'SECURITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'SECURITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -240,7 +240,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'RELIABILITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'RELIABILITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -328,7 +328,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key = ANY(ARRAY[${project_id}]::text[]) AND cii.software_quality = 'MAINTAINABILITY' AND ci.severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'MAINTAINABILITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -430,7 +430,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -519,7 +519,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100, 2), '%') AS \"Reviewed\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'HOTSPOTS' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -634,7 +634,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1), '% ', 'Coverage on ', ROUND(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines to cover') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -724,7 +724,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -814,7 +814,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)), \" day(s) \", FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)), \" hour(s) \") AS \"Debt\" FROM cq_issues WHERE project_key = ANY(ARRAY[${project_id}]::text[]) AND type = 'CODE_SMELL' AND severity = ANY(ARRAY[${severity}]::text[])", + "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -929,7 +929,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1018,7 +1018,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT CONCAT(ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1), '% ', 'Duplications on ', ROUND(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0), 0), 'k Lines') FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1121,7 +1121,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(ncloc) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT SUM(ncloc) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1211,7 +1211,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(num_of_lines) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT SUM(num_of_lines) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1301,7 +1301,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT file_path) FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT file_path) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", "refId": "A", "sql": { "columns": [ @@ -1405,7 +1405,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", CONCAT(ROUND(coverage, 2), '%') AS \"Coverage\", CONCAT(ROUND(duplicated_lines_density, 2), '%') AS \"Duplications\" FROM cq_file_metrics WHERE project_key = ANY(ARRAY[${project_id}]::text[]) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ @@ -1449,14 +1449,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) as text from cq_projects", + "definition": "SELECT name || '--' || id AS text FROM cq_projects", "hide": 0, "includeAll": true, "label": "SonarQube Project", "multi": true, "name": "project_id", "options": [], - "query": "select concat(name, '--', id) as text from cq_projects", + "query": "SELECT name || '--' || id AS text FROM cq_projects", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1474,14 +1474,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct severity from cq_issues where id like 'sonar%'", + "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", "hide": 0, "includeAll": true, "label": "Severity", "multi": true, "name": "severity", "options": [], - "query": "select distinct severity from cq_issues where id like 'sonar%'", + "query": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -1496,7 +1496,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "SonarQube Cloud (PostgreSQL)", + "title": "SonarQube Cloud", "uid": "WA0qbuJ4l-pg", "version": 1, "weekStart": "" diff --git a/grafana/dashboards/postgresql/SteeringAdoptionTracker.json b/grafana/dashboards/postgresql/steering-adoption-tracker.json similarity index 83% rename from grafana/dashboards/postgresql/SteeringAdoptionTracker.json rename to grafana/dashboards/postgresql/steering-adoption-tracker.json index 301cf5af7cf..fdbc522339e 100644 --- a/grafana/dashboards/postgresql/SteeringAdoptionTracker.json +++ b/grafana/dashboards/postgresql/steering-adoption-tracker.json @@ -62,7 +62,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(DISTINCT CASE WHEN has_steering = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) AS DECIMAL), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -113,7 +113,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(COUNT(DISTINCT CASE WHEN is_spec_mode = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(DISTINCT CASE WHEN is_spec_mode = TRUE THEN user_id END) * 100.0 AS NUMERIC) / NULLIF(NULLIF(COUNT(DISTINCT user_id), 0), 0) AS DECIMAL), 0) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -164,7 +164,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", + "rawSql": "SELECT ROUND(CAST(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS DECIMAL), 1) AS \"value\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp)", "refId": "A" } ], @@ -223,7 +223,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Steering Rate\", CAST(SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Spec Mode Rate\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM timestamp) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) - 1) * INTERVAL '1 day' AS time, CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Steering Rate\", CAST(SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Spec Mode Rate\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY CAST(timestamp AS DATE) - (EXTRACT(ISODOW FROM CAST(timestamp AS DATE)) - 1) * INTERVAL '1 day' ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -321,7 +321,7 @@ "datasource": "postgresql", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", COUNT(*) AS \"Total Chats\", SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS \"Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS \"Spec Mode\", ROUND(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0), 1) AS \"Steering %\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY user_id ORDER BY COUNT(*) DESC NULLS LAST", + "rawSql": "SELECT COALESCE(MAX(display_name), user_id) AS \"User\", COUNT(*) AS \"Total Chats\", SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) AS \"Steering\", SUM(CASE WHEN is_spec_mode = TRUE THEN 1 ELSE 0 END) AS \"Spec Mode\", ROUND(CAST(CAST(SUM(CASE WHEN has_steering = TRUE THEN 1 ELSE 0 END) * 100.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS DECIMAL), 1) AS \"Steering %\" FROM _tool_q_dev_chat_log WHERE $__timeFilter(timestamp) GROUP BY \"user_id\" ORDER BY COUNT(*) DESC NULLS LAST", "refId": "A" } ], @@ -347,7 +347,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Kiro Steering & Spec Mode Adoption (PostgreSQL)", + "title": "Kiro Steering & Spec Mode Adoption", "uid": "kiro_steering_adoption-pg", "version": 1 } \ No newline at end of file diff --git a/grafana/dashboards/postgresql/WeeklyBugRetro.json b/grafana/dashboards/postgresql/weekly-bug-retro.json similarity index 78% rename from grafana/dashboards/postgresql/WeeklyBugRetro.json rename to grafana/dashboards/postgresql/weekly-bug-retro.json index 53585d93cfa..10b3026e897 100644 --- a/grafana/dashboards/postgresql/WeeklyBugRetro.json +++ b/grafana/dashboards/postgresql/weekly-bug-retro.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly New Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly New Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -302,7 +302,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT CONCAT(TO_CHAR(cw.start_of_week, '%m/%d'), ' - ', TO_CHAR(cw.start_of_week + INTERVAL '7 DAY', '%m/%d')) AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly Closed Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly Closed Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -442,7 +442,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST), created_bugs AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), resolved_bugs AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id = ANY(ARRAY[${board_id}]::text[]) GROUP BY time ORDER BY time DESC NULLS LAST), weekly_new_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_new_bug FROM calendar_weeks AS cw LEFT JOIN created_bugs AS cb ON cw.start_of_week = cb.time), weekly_closed_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_closed_bug FROM calendar_weeks AS cw LEFT JOIN resolved_bugs AS cb ON cw.start_of_week = cb.time), weekly_updates AS (SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 LEFT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week UNION SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 RIGHT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week), original_open_bugs AS (SELECT COUNT(DISTINCT i.id) AS original_open_bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND i.created_date < $__timeFrom() AND (i.status <> 'DONE' OR $__timeFilter(i.resolution_date)) AND b.id = ANY(ARRAY[${board_id}]::text[])), weekly_updated_without_null AS (SELECT week, COALESCE(weekly_new_bug, 0) AS weekly_new_bug, COALESCE(weekly_closed_bug, 0) AS weekly_closed_bug, original_open_bug_count FROM weekly_updates, original_open_bugs WHERE NOT week IS NULL), weekly_delta AS (SELECT *, (weekly_new_bug - weekly_closed_bug) AS weekly_delta FROM weekly_updated_without_null ORDER BY week ASC NULLS FIRST), final_data AS (SELECT *, CONCAT(TO_CHAR(week, '%m/%d'), ' - ', TO_CHAR(week + INTERVAL '7 DAY', '%m/%d')) AS _week, SUM(weekly_delta) OVER (ORDER BY week ASC NULLS FIRST) AS weekly_accumulated FROM weekly_delta) SELECT _week, (original_open_bug_count + weekly_accumulated) AS \"Total No. of Outstanding Bugs By the End of Week\" FROM final_data", + "rawSql": "WITH calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST), created_bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), resolved_bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), weekly_new_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_new_bug FROM calendar_weeks AS cw LEFT JOIN created_bugs AS cb ON cw.start_of_week = cb.time), weekly_closed_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_closed_bug FROM calendar_weeks AS cw LEFT JOIN resolved_bugs AS cb ON cw.start_of_week = cb.time), weekly_updates AS (SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 LEFT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week UNION SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 RIGHT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week), original_open_bugs AS (SELECT COUNT(DISTINCT i.id) AS original_open_bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND i.created_date < $__timeFrom() AND (i.status <> 'DONE' OR $__timeFilter(i.resolution_date)) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)), weekly_updated_without_null AS (SELECT week, COALESCE(weekly_new_bug, 0) AS weekly_new_bug, COALESCE(weekly_closed_bug, 0) AS weekly_closed_bug, original_open_bug_count FROM weekly_updates, original_open_bugs WHERE NOT week IS NULL), weekly_delta AS (SELECT *, (weekly_new_bug - weekly_closed_bug) AS weekly_delta FROM weekly_updated_without_null ORDER BY week ASC NULLS FIRST), final_data AS (SELECT *, TO_CHAR(CAST(week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS _week, SUM(weekly_delta) OVER (ORDER BY week ASC NULLS FIRST) AS weekly_accumulated FROM weekly_delta) SELECT _week, (original_open_bug_count + weekly_accumulated) AS \"Total No. of Outstanding Bugs By the End of Week\" FROM final_data", "refId": "A", "select": [ [ @@ -564,7 +564,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -676,7 +676,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS \"Issue Number\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' GROUP BY 1", + "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS \"Issue Number\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' GROUP BY 1", "refId": "A", "select": [ [ @@ -835,7 +835,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", i.url AS \"Url\", i.creator_name AS \"Creator\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", i.url AS \"Url\", i.creator_name AS \"Creator\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -944,7 +944,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1058,7 +1058,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1231,7 +1231,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS \"Lead Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS \"Lead Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1371,7 +1371,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CONCAT('#', i.issue_key, ' ', i.title) AS issue_key, CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id = ANY(ARRAY[${board_id}]::text[]) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 day' AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL 'WEEKDAY DAY' ORDER BY lead_time DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT '#' || i.issue_key || ' ' || i.title AS issue_key, CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' ORDER BY lead_time DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1480,7 +1480,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1588,7 +1588,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[])", + "rawSql": "SELECT AVG(CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1768,7 +1768,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) AND priority = ANY(ARRAY[${priority}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1908,7 +1908,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CONCAT('#', i.issue_key) AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 2 DESC NULLS LAST LIMIT 100", + "rawSql": "SELECT '#' || i.issue_key AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY 2 DESC NULLS LAST LIMIT 100", "refId": "A", "select": [ [ @@ -2105,7 +2105,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id = ANY(ARRAY[${board_id}]::text[]) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ @@ -2199,14 +2199,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select concat(name, '-', id) as text from boards", + "definition": "SELECT name || '-' || id AS text FROM boards", "hide": 0, "includeAll": true, "label": "Board", "multi": false, "name": "board_id", "options": [], - "query": "select concat(name, '-', id) as text from boards", + "query": "SELECT name || '-' || id AS text FROM boards", "refresh": 1, "regex": "/^(?.*)-(?.*)$/", "skipUrlSync": false, @@ -2220,14 +2220,14 @@ "value": "BUG" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "issue_type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2245,14 +2245,14 @@ ] }, "datasource": "postgresql", - "definition": "select distinct priority from issues", + "definition": "SELECT DISTINCT priority FROM issues", "hide": 0, "includeAll": true, "label": "Priority", "multi": true, "name": "priority", "options": [], - "query": "select distinct priority from issues", + "query": "SELECT DISTINCT priority FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2267,7 +2267,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Weekly Bug Retro (PostgreSQL)", + "title": "Weekly Bug Retro", "uid": "-5EKA5w7k-pg", "version": 5, "weekStart": "" diff --git a/grafana/dashboards/postgresql/WeeklyCommunityRetro.json b/grafana/dashboards/postgresql/weekly-community-retro.json similarity index 83% rename from grafana/dashboards/postgresql/WeeklyCommunityRetro.json rename to grafana/dashboards/postgresql/weekly-community-retro.json index aec3625dced..4eac61915f2 100644 --- a/grafana/dashboards/postgresql/WeeklyCommunityRetro.json +++ b/grafana/dashboards/postgresql/weekly-community-retro.json @@ -147,7 +147,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -254,7 +254,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -362,7 +362,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(SUM(CASE WHEN NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS community_issue_ratio FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS community_issue_ratio FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -472,7 +472,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT DISTINCT i.creator_name FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_name IN (SELECT DISTINCT creator_name FROM issues WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT creator_name IS NULL)", + "rawSql": "SELECT DISTINCT i.creator_name FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_name IN (SELECT DISTINCT creator_name FROM issues WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT creator_name IS NULL)", "refId": "A", "select": [ [ @@ -566,7 +566,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT 1 - CAST(COUNT(DISTINCT CASE WHEN comment_id IS NULL THEN issue_id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT issue_id), 0) AS response_rate FROM issue_comment_list", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT 1 - CAST(COUNT(DISTINCT CASE WHEN comment_id IS NULL THEN issue_id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT issue_id), 0) AS response_rate FROM issue_comment_list", "refId": "A", "select": [ [ @@ -736,7 +736,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"queue_time_in_days\", url FROM issue_comment_list WHERE comment_id IS NULL", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"queue_time_in_days\", url FROM issue_comment_list WHERE comment_id IS NULL", "refId": "A", "select": [ [ @@ -846,7 +846,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT SUBSTRING_INDEX(i.url, '/', -1) AS issue_number, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type = ANY(ARRAY[${issue_type}]::text[]) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND b.id = ANY(ARRAY[${repo_id}]::text[]) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))) SELECT issue_key, title, creator_name /* body, */, issue_created_date, comment_date, CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS response_time_in_days, url FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT REVERSE(SPLIT_PART(REVERSE(i.url), '/', 1)) AS issue_number, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT issue_key, title, creator_name /* body, */, issue_created_date, comment_date, CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS response_time_in_days, url FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -1153,7 +1153,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1260,7 +1260,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -1367,7 +1367,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(SUM(CASE WHEN NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS community_pr_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[])", + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS community_pr_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", "refId": "A", "select": [ [ @@ -1474,7 +1474,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -1581,7 +1581,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -1675,7 +1675,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT DISTINCT author_name FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND NOT merged_date IS NULL AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_name IN (SELECT DISTINCT author_name FROM pull_requests WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT author_name IS NULL)", + "rawSql": "SELECT DISTINCT author_name FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND NOT merged_date IS NULL AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_name IN (SELECT DISTINCT author_name FROM pull_requests WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT author_name IS NULL)", "refId": "A", "select": [ [ @@ -1787,7 +1787,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS merged_pull_request_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS merged_pull_request_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -1926,7 +1926,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pull_request_key, title, status, author_name, created_date, CAST(((EXTRACT(EPOCH FROM (CURRENT_DATE - created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, url FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[]))", + "rawSql": "SELECT pull_request_key, title, status, author_name, created_date, CAST(((EXTRACT(EPOCH FROM (CURRENT_DATE - created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, url FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", "refId": "A", "select": [ [ @@ -2064,7 +2064,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - INTERVAL 'WEEKDAY DAY' AND base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization = ANY(ARRAY[${org}]::text[])) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -2166,7 +2166,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT author_id) AS all_contributor_count FROM pull_requests AS pr WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization = ANY(ARRAY[${org}]::text[])) */", + "rawSql": "SELECT COUNT(DISTINCT author_id) AS all_contributor_count FROM pull_requests AS pr WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) */", "refId": "A", "select": [ [ @@ -2273,7 +2273,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE base_repo_id = ANY(ARRAY[${repo_id}]::text[]) AND NOT merged_date IS NULL GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -2371,14 +2371,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(name, '-', id) as text from repos", + "definition": "SELECT name || '-' || id AS text FROM repos", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "select concat(name, '-', id) as text from repos", + "query": "SELECT name || '-' || id AS text FROM repos", "refresh": 1, "regex": "/^(?.*)-(?.*)$/", "skipUrlSync": false, @@ -2392,14 +2392,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "select distinct type from issues", + "definition": "SELECT DISTINCT type FROM issues", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": false, "name": "issue_type", "options": [], - "query": "select distinct type from issues", + "query": "SELECT DISTINCT type FROM issues", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2417,7 +2417,7 @@ ] }, "datasource": "postgresql", - "definition": "select distinct organization from accounts where organization != ''", + "definition": "SELECT DISTINCT organization FROM accounts WHERE organization <> ''", "description": "", "hide": 0, "includeAll": true, @@ -2425,7 +2425,7 @@ "multi": true, "name": "org", "options": [], - "query": "select distinct organization from accounts where organization != ''", + "query": "SELECT DISTINCT organization FROM accounts WHERE organization <> ''", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2440,7 +2440,7 @@ }, "timepicker": {}, "timezone": "utc", - "title": "Weekly Community Retro (PostgreSQL)", + "title": "Weekly Community Retro", "uid": "VTr6Y_q7z-pg", "version": 2, "weekStart": "" diff --git a/grafana/dashboards/postgresql/WorkLogs.json b/grafana/dashboards/postgresql/work-logs.json similarity index 66% rename from grafana/dashboards/postgresql/WorkLogs.json rename to grafana/dashboards/postgresql/work-logs.json index 16137645920..86949c30b3d 100644 --- a/grafana/dashboards/postgresql/WorkLogs.json +++ b/grafana/dashboards/postgresql/work-logs.json @@ -216,7 +216,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(resolution_date, '%Y-%m-%d | %W') AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(authored_date, '%Y-%m-%d | %W') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(created_date, '%Y-%m-%d | %W') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(merged_date, '%Y-%m-%d | %W') AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(prc.created_date, '%Y-%m-%d | %W') AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN 'Date' ELSE NULL END AS \"Date\", \"Time\", Activity, Details, Name FROM _activities", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(resolution_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(merged_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN 'Date' ELSE NULL END AS \"Date\", 'Time', \"Activity\", \"Details\", \"Name\" FROM _activities", "refId": "A", "sql": { "columns": [ @@ -359,7 +359,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activity_count_per_day AS (SELECT Date, COUNT(*) AS value FROM _activities GROUP BY 1), last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d, TO_CHAR(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE), 'w%u %Y') AS week_name, TO_CHAR(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE), '%Y%u') AS week_number FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _calendar_months_with_rank AS (SELECT d, CONCAT(week_name, ' (', TO_CHAR(d - INTERVAL 'WEEKDAY DAY', '%m/%d'), ' ~ ', TO_CHAR(d - INTERVAL 'WEEKDAY DAY' + INTERVAL '6 DAY', '%m/%d'), ')') AS week_name, week_number, TO_CHAR(d, '%W') AS weekday, DENSE_RANK() OVER (ORDER BY week_number DESC NULLS LAST) AS week_rank FROM last_few_calendar_months ORDER BY 1 DESC NULLS LAST), _final_dataset AS (SELECT _calendar_months_with_rank.*, CASE WHEN _activity_count_per_day.value IS NULL THEN 0 ELSE _activity_count_per_day.value END AS activity_count FROM _calendar_months_with_rank LEFT JOIN _activity_count_per_day ON _calendar_months_with_rank.d = _activity_count_per_day.Date), WeekSummary AS (SELECT week_name, SUM(CASE WHEN weekday = 'Monday' THEN activity_count END) AS Mon, SUM(CASE WHEN weekday = 'Tuesday' THEN activity_count END) AS Tue, SUM(CASE WHEN weekday = 'Wednesday' THEN activity_count END) AS Wed, SUM(CASE WHEN weekday = 'Thursday' THEN activity_count END) AS Thur, SUM(CASE WHEN weekday = 'Friday' THEN activity_count END) AS Fri, SUM(CASE WHEN weekday = 'Saturday' THEN activity_count END) AS Sat, SUM(CASE WHEN weekday = 'Sunday' THEN activity_count END) AS Sun FROM _final_dataset WHERE week_rank BETWEEN 1 AND 52 GROUP BY week_name) SELECT week_name, Mon, Tue, Wed, Thur, Fri, Sat, Sun FROM WeekSummary ORDER BY CAST(SUBSTRING(week_name FROM STRPOS(week_name, ' ') + 1 FOR 4) AS UBIGINT) DESC NULLS LAST, CAST(SUBSTRING(week_name FROM 2 FOR STRPOS(week_name, ' ') - 2) AS UBIGINT) DESC NULLS LAST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activity_count_per_day AS (SELECT \"Date\", COUNT(*) AS value FROM _activities GROUP BY 1), last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'wIW YYYY') AS week_name, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'YYYYIW') AS week_number FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _calendar_months_with_rank AS (SELECT d, week_name || ' (' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' AS TIMESTAMP), 'MM/DD') || ' ~ ' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') || ')' AS week_name, \"week_number\", TO_CHAR(CAST(d AS TIMESTAMP), 'FMDay') AS weekday, DENSE_RANK() OVER (ORDER BY week_number DESC NULLS LAST) AS week_rank FROM last_few_calendar_months ORDER BY 1 DESC NULLS LAST), _final_dataset AS (SELECT _calendar_months_with_rank.*, CASE WHEN _activity_count_per_day.value IS NULL THEN 0 ELSE _activity_count_per_day.value END AS activity_count FROM _calendar_months_with_rank LEFT JOIN _activity_count_per_day ON _calendar_months_with_rank.d = _activity_count_per_day.\"Date\"), WeekSummary AS (SELECT week_name, SUM(CASE WHEN weekday = 'Monday' THEN activity_count END) AS \"Mon\", SUM(CASE WHEN weekday = 'Tuesday' THEN activity_count END) AS \"Tue\", SUM(CASE WHEN weekday = 'Wednesday' THEN activity_count END) AS \"Wed\", SUM(CASE WHEN weekday = 'Thursday' THEN activity_count END) AS \"Thur\", SUM(CASE WHEN weekday = 'Friday' THEN activity_count END) AS \"Fri\", SUM(CASE WHEN weekday = 'Saturday' THEN activity_count END) AS \"Sat\", SUM(CASE WHEN weekday = 'Sunday' THEN activity_count END) AS \"Sun\" FROM _final_dataset WHERE week_rank BETWEEN 1 AND 52 GROUP BY week_name) SELECT week_name, \"Mon\", \"Tue\", \"Wed\", \"Thur\", \"Fri\", \"Sat\", \"Sun\" FROM WeekSummary ORDER BY CAST(SUBSTRING(week_name FROM POSITION(' ' IN week_name) + 1 FOR 4) AS BIGINT) DESC NULLS LAST, CAST(SUBSTRING(week_name FROM 2 FOR POSITION(' ' IN week_name) - 2) AS BIGINT) DESC NULLS LAST", "refId": "A", "sql": { "columns": [ @@ -580,7 +580,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS Date, resolution_date AS Time, 'Issue resolved' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS Date, merged_date AS Time, 'PR gets merged' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS Date, prc.created_date AS Time, 'Review PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY Time DESC NULLS LAST), _activities_per_day AS (SELECT Date, SUM(CASE WHEN Activity = 'Create an issue' THEN 1 ELSE 0 END) AS \"Create an issue\", SUM(CASE WHEN Activity = 'Issue resolved' THEN 1 ELSE 0 END) AS \"Issue resolved\", SUM(CASE WHEN Activity = 'Finish a commit' THEN 1 ELSE 0 END) AS \"Finish a commit\", SUM(CASE WHEN Activity = 'Open a PR' THEN 1 ELSE 0 END) AS \"Open a PR\", SUM(CASE WHEN Activity = 'PR gets merged' THEN 1 ELSE 0 END) AS \"PR gets merged\", SUM(CASE WHEN Activity = 'Comment on PR' THEN 1 ELSE 0 END) AS \"Comment on PR\", SUM(CASE WHEN Activity = 'Review PR' THEN 1 ELSE 0 END) AS \"Review PR\" FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS Date FROM (SELECT 0 AS H UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS H CROSS JOIN (SELECT 0 AS T UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS T CROSS JOIN (SELECT 0 AS U UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS U WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY Date DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(_last_7_days.Date, '%d/%m %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.Date = _activities_per_day.Date ORDER BY _last_7_days.Date ASC NULLS FIRST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activities_per_day AS (SELECT \"Date\", SUM(CASE WHEN \"Activity\" = 'Create an issue' THEN 1 ELSE 0 END) AS \"Create an issue\", SUM(CASE WHEN \"Activity\" = 'Issue resolved' THEN 1 ELSE 0 END) AS \"Issue resolved\", SUM(CASE WHEN \"Activity\" = 'Finish a commit' THEN 1 ELSE 0 END) AS \"Finish a commit\", SUM(CASE WHEN \"Activity\" = 'Open a PR' THEN 1 ELSE 0 END) AS \"Open a PR\", SUM(CASE WHEN \"Activity\" = 'PR gets merged' THEN 1 ELSE 0 END) AS \"PR gets merged\", SUM(CASE WHEN \"Activity\" = 'Comment on PR' THEN 1 ELSE 0 END) AS \"Comment on PR\", SUM(CASE WHEN \"Activity\" = 'Review PR' THEN 1 ELSE 0 END) AS \"Review PR\" FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS \"Date\" FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY \"Date\" DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(CAST(_last_7_days.\"Date\" AS TIMESTAMP), 'DD/MM %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.\"Date\" = _activities_per_day.\"Date\" ORDER BY _last_7_days.\"Date\" ASC NULLS FIRST", "refId": "A", "sql": { "columns": [ @@ -716,7 +716,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _issues AS (SELECT TO_CHAR(created_date, '%d/%m/%Y') AS Date, created_date AS Time, 'Create an issue' AS Activity, CONCAT('#', issue_key, ' ', title) AS Details, status, a.name AS Name FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date)) SELECT status, COUNT(*) FROM _issues GROUP BY 1", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _issues AS (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", status, a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date)) SELECT status, COUNT(*) FROM _issues GROUP BY 1", "refId": "A", "sql": { "columns": [ @@ -787,7 +787,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name, u.email FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT COUNT(DISTINCT c.sha) FROM commits AS c JOIN _accounts AS a ON c.author_id = a.email WHERE $__timeFilter(authored_date)", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name, u.email FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)) SELECT COUNT(DISTINCT c.sha) FROM commits AS c JOIN _accounts AS a ON c.author_id = a.email WHERE $__timeFilter(authored_date)", "refId": "A", "sql": { "columns": [ @@ -859,7 +859,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)) SELECT SUM(additions + deletions) FROM _commits", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)) SELECT SUM(additions + deletions) FROM _commits", "refId": "A", "sql": { "columns": [ @@ -931,7 +931,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT pr.id AS pr_id, pr.merged_date, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS details, prc.id AS comment_id, prc.created_date AS comment_date, a.name, prc.type AS comment_type FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id LEFT JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(pr.created_date)) SELECT CONCAT(COUNT(DISTINCT CASE WHEN NOT name IS NULL THEN pr_id END), '/', COUNT(DISTINCT pr_id)) AS text FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT pr.id AS pr_id, pr.merged_date, '#' || pr.pull_request_key || ' ' || pr.title AS details, prc.id AS comment_id, prc.created_date AS comment_date, a.name, prc.type AS comment_type FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id LEFT JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(pr.created_date)) SELECT COUNT(DISTINCT CASE WHEN NOT name IS NULL THEN pr_id END) || '/' || COUNT(DISTINCT pr_id) AS text FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1003,7 +1003,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT DISTINCT TO_CHAR(created_date, '%d/%m/%Y') AS Date, created_date AS Time, 'Open a PR' AS Activity, CONCAT('#', pull_request_key, ' ', title) AS Details, a.name AS Name, pr.id, pr.merged_date FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date)) SELECT CONCAT(COUNT(CASE WHEN NOT merged_date IS NULL THEN id ELSE NULL END), '/', COUNT(DISTINCT id)) FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT DISTINCT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\", pr.id, pr.merged_date FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date)) SELECT COUNT(CASE WHEN NOT merged_date IS NULL THEN id ELSE NULL END) || '/' || COUNT(DISTINCT id) FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1121,7 +1121,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 day' AS time, COUNT(DISTINCT pr.id) AS pr_count FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) AND /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -DAY($__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ NOT pr.merged_date IS NULL GROUP BY 1) SELECT TO_CHAR(time, '%M %Y') AS month, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_count FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) AND /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ NOT pr.merged_date IS NULL GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1226,7 +1226,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _commits AS (SELECT DISTINCT TO_CHAR(authored_date, '%d/%m/%Y') AS Date, authored_date AS Time, 'Finish a commit' AS Activity, CONCAT(message, ' #', sha) AS Details, a.name AS Name, c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS \"PR Merged Size\" FROM _pr_commits_data", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS \"PR Merged Size\" FROM _pr_commits_data", "refId": "A", "sql": { "columns": [ @@ -1298,7 +1298,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)), _prs AS (SELECT TO_CHAR(prc.created_date, '%d/%m/%Y') AS Date, prc.created_date AS Time, 'Comment on PR' AS Activity, CONCAT('#', pr.pull_request_key, ' ', pr.title) AS Details, a.name AS Name, pr.id AS pr_id, prc.id AS prc_id FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(prc.created_date)) SELECT ROUND(CAST(COUNT(DISTINCT prc_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0), 1) FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\", pr.id AS pr_id, prc.id AS prc_id FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(prc.created_date)) SELECT ROUND(CAST(CAST(COUNT(DISTINCT prc_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS DECIMAL), 1) FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1371,7 +1371,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id IN ($users)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS \"Time to merge\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS \"Time to merge\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", "refId": "A", "sql": { "columns": [ @@ -1412,14 +1412,14 @@ "value": "" }, "datasource": "postgresql", - "definition": "select concat(name, '--', id) from teams", + "definition": "SELECT name || '--' || id FROM teams", "hide": 0, "includeAll": false, "label": "Team", "multi": false, "name": "team", "options": [], - "query": "select concat(name, '--', id) from teams", + "query": "SELECT name || '--' || id FROM teams", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1437,14 +1437,14 @@ ] }, "datasource": "postgresql", - "definition": "select concat(users.name, '--', users.id) from users left join team_users on users.id = team_users.user_id where team_users.team_id in ($team)", + "definition": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE team_users.team_id::text IN (SELECT v FROM unnest(CASE WHEN '$team' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$team]::text[] END) v)", "hide": 0, "includeAll": true, "label": "User", "multi": true, "name": "users", "options": [], - "query": "select concat(users.name, '--', users.id) from users left join team_users on users.id = team_users.user_id where team_users.team_id in ($team)", + "query": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE team_users.team_id::text IN (SELECT v FROM unnest(CASE WHEN '$team' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$team]::text[] END) v)", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -1460,7 +1460,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "utc", - "title": "Work Logs (PostgreSQL)", + "title": "Work Logs", "uid": "d449042e-22f0-4357-b8b7-22083f47618d-pg", "version": 3, "weekStart": "" From 520e33f2ff3886d0212bca9811b94837311295a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:56:06 -0300 Subject: [PATCH 07/11] feat(docker): split mysql and postgresql compose configurations - Rename docker-compose-dev.yml to docker-compose-dev-postgresql.yml - Create separate docker-compose-dev-mysql.yml - Use different project names to avoid conflicts - Assign unique ports for each stack (MySQL: 3001/8083/4001/4181, PostgreSQL: 3002/8084/4000/4180) --- ...v.yml => docker-compose-dev-postgresql.yml | 58 ++----------------- 1 file changed, 5 insertions(+), 53 deletions(-) rename docker-compose-dev.yml => docker-compose-dev-postgresql.yml (63%) diff --git a/docker-compose-dev.yml b/docker-compose-dev-postgresql.yml similarity index 63% rename from docker-compose-dev.yml rename to docker-compose-dev-postgresql.yml index 7f97a8f77d0..eff85477723 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev-postgresql.yml @@ -13,26 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version: "3" -services: - mysql: - image: mysql:8 - volumes: - - mysql-storage:/var/lib/mysql - restart: always - ports: - - 3306:3306 - environment: - MYSQL_ROOT_PASSWORD: admin - MYSQL_DATABASE: lake - MYSQL_USER: merico - MYSQL_PASSWORD: merico - TZ: UTC - command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin - --skip-log-bin +name: devlake-postgresql +services: postgres: - image: postgres:14.2 + image: postgres:17.2 volumes: - postgres-storage:/var/lib/postgresql restart: always @@ -44,19 +29,6 @@ services: POSTGRES_PASSWORD: merico TZ: UTC - postgres17: - image: postgres:17.2 - volumes: - - postgres17-storage:/var/lib/postgresql - restart: always - ports: - - 5433:5432 - environment: - POSTGRES_DB: lake - POSTGRES_USER: merico - POSTGRES_PASSWORD: merico - TZ: UTC - grafana: image: devlake.docker.scarf.sh/apache/devlake-dashboard:latest build: @@ -65,10 +37,10 @@ services: - 3002:3000 volumes: - grafana-storage:/var/lib/grafana + - ./grafana/dashboards/postgresql:/etc/grafana/dashboards:ro environment: GF_SERVER_ROOT_URL: "http://localhost:4000/grafana" GF_USERS_DEFAULT_THEME: "light" - # Database configuration (choose mysql or postgresql) DATABASE_TYPE: postgresql DATABASE_HOST: postgres DATABASE_PORT: 5432 @@ -102,7 +74,6 @@ services: FORCE_MIGRATION: "true" LOGGING_DIR: /app/logs TZ: UTC - # LOGOUT_URI: https://xxx.amazoncognito.com/logout?client_id=yyy&logout_uri=http%3A%2F%2Flocalhost%3A4180%2Foauth2%2Fsign_out depends_on: - postgres @@ -118,8 +89,6 @@ services: DEVLAKE_ENDPOINT: devlake:8080 GRAFANA_ENDPOINT: grafana:3000 TZ: UTC - #ADMIN_USER: devlake - #ADMIN_PASS: letsdivein depends_on: - devlake @@ -130,25 +99,8 @@ services: - 4180:4180 env_file: - ./.env - # environment: - # OAUTH2_PROXY_PROVIDER: oidc - # OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: my provider - # OAUTH2_PROXY_COOKIE_SECRET: - # OAUTH2_PROXY_COOKIE_DOMAINS: localhost:4180 - # OAUTH2_PROXY_COOKIE_SECURE: 'false' - # OAUTH2_PROXY_EMAIL_DOMAINS: * - # OAUTH2_PROXY_OIDC_ISSUER_URL: - # OAUTH2_PROXY_OIDC_JWKS_URL: - # OAUTH2_PROXY_CLIENT_ID: - # OAUTH2_PROXY_CLIENT_SECRET: - # OAUTH2_PROXY_UPSTREAMS: http://localhost:4000 - # OAUTH2_PROXY_HTTP_ADDRESS: http://0.0.0.0:4180 - # OAUTH2_PROXY_REVERSE_PROXY: 'true' - # OAUTH2_PROXY_SKIP_AUTH_ROUTES: ^/grafana.* volumes: - mysql-storage: - grafana-storage: postgres-storage: - postgres17-storage: + grafana-storage: devlake-log: From 34b1df3a1d77f84278bf1aa2d56399b0f2b1a394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:56:18 -0300 Subject: [PATCH 08/11] feat(docker): add mysql development compose configuration Create dedicated compose file for MySQL development environment with unique ports and project name --- docker-compose-dev-mysql.yml | 110 +++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docker-compose-dev-mysql.yml diff --git a/docker-compose-dev-mysql.yml b/docker-compose-dev-mysql.yml new file mode 100644 index 00000000000..52561e442f2 --- /dev/null +++ b/docker-compose-dev-mysql.yml @@ -0,0 +1,110 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: devlake-mysql + +services: + mysql: + image: mysql:8 + volumes: + - mysql-storage:/var/lib/mysql + restart: always + ports: + - 3306:3306 + environment: + MYSQL_ROOT_PASSWORD: admin + MYSQL_DATABASE: lake + MYSQL_USER: merico + MYSQL_PASSWORD: merico + TZ: UTC + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin + --skip-log-bin + + grafana: + image: devlake.docker.scarf.sh/apache/devlake-dashboard:latest + build: + context: grafana/ + ports: + - 3001:3000 + volumes: + - grafana-storage:/var/lib/grafana + - ./grafana/dashboards/mysql:/etc/grafana/dashboards:ro + environment: + GF_SERVER_ROOT_URL: "http://localhost:4001/grafana" + GF_USERS_DEFAULT_THEME: "light" + DATABASE_TYPE: mysql + DATABASE_HOST: mysql + DATABASE_PORT: 3306 + DATABASE_NAME: lake + DATABASE_USER: merico + DATABASE_PASSWORD: merico + TZ: UTC + restart: always + depends_on: + - mysql + + devlake: + image: devlake-local:latest + build: + context: backend + dockerfile: Dockerfile.local + args: + HTTPS_PROXY: "${HTTPS_PROXY}" + GOPROXY: "${GOPROXY}" + ports: + - 8083:8080 + restart: always + volumes: + - devlake-log:/app/logs + env_file: + - ./.env + environment: + DB_URL: mysql://merico:merico@mysql:3306/lake?charset=utf8mb4&parseTime=True&loc=UTC + E2E_DB_URL: mysql://merico:merico@mysql:3306/lake_test?charset=utf8mb4&parseTime=True&loc=UTC + REMOTE_PLUGIN_DIR: "" + FORCE_MIGRATION: "true" + LOGGING_DIR: /app/logs + TZ: UTC + depends_on: + - mysql + + config-ui: + image: devlake.docker.scarf.sh/apache/devlake-config-ui:latest + build: + context: "config-ui" + ports: + - 4001:4000 + env_file: + - ./.env + environment: + DEVLAKE_ENDPOINT: devlake:8080 + GRAFANA_ENDPOINT: grafana:3000 + TZ: UTC + depends_on: + - devlake + + authproxy: + image: quay.io/oauth2-proxy/oauth2-proxy:v7.4.0-amd64 + network_mode: "host" + ports: + - 4181:4180 + env_file: + - ./.env + +volumes: + mysql-storage: + grafana-storage: + devlake-log: From c7957ff34630425a5ef59e0f4091481d413833a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:56:30 -0300 Subject: [PATCH 09/11] fix(grafana): disable folder structure from dashboard provisioning Set foldersFromFilesStructure to false to prevent creating postgresql/mysql subdirectories in Grafana UI --- grafana/provisioning/dashboards/dashboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana/provisioning/dashboards/dashboard.yml b/grafana/provisioning/dashboards/dashboard.yml index 3e10a42cba7..72df91b7701 100644 --- a/grafana/provisioning/dashboards/dashboard.yml +++ b/grafana/provisioning/dashboards/dashboard.yml @@ -38,4 +38,4 @@ providers: # path to dashboard files on disk. Required path: /etc/grafana/dashboards # use folder names from filesystem to create folders in Grafana - foldersFromFilesStructure: true + foldersFromFilesStructure: false From 26a56bf0533ea7dc345954522a0817a55dd96e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Mon, 11 May 2026 15:56:30 -0300 Subject: [PATCH 10/11] refactor(grafana): combine RUN commands in Dockerfile Merge plugin installation and permission setup into single RUN statement --- grafana/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafana/Dockerfile b/grafana/Dockerfile index 7a316b81270..26745c5336b 100644 --- a/grafana/Dockerfile +++ b/grafana/Dockerfile @@ -34,8 +34,8 @@ ENV GF_SERVER_SERVE_FROM_SUB_PATH=true ENV GF_DASHBOARDS_JSON_ENABLED=true ENV GF_LIVE_ALLOWED_ORIGINS='*' USER root -RUN grafana-cli plugins install grafana-piechart-panel -RUN chmod +x /entrypoint.sh && \ +RUN grafana-cli plugins install grafana-piechart-panel && \ + chmod +x /entrypoint.sh && \ chgrp -R 0 /etc/grafana /usr/share/grafana /var/lib/grafana && \ chmod -R g=u /etc/grafana /usr/share/grafana /var/lib/grafana USER 101 From 2799bcffb5a72cb5488ccdb3d837dafd2d3aed9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Luciano?= Date: Tue, 19 May 2026 14:56:05 -0300 Subject: [PATCH 11/11] fix: fixing more dashboards --- grafana/dashboards/mysql/dora-debug.json | 2 +- grafana/dashboards/postgresql/Asana.json | 22 ++-- grafana/dashboards/postgresql/Bamboo.json | 20 +-- grafana/dashboards/postgresql/DORA.json | 18 +-- grafana/dashboards/postgresql/Gitlab.json | 36 +++--- grafana/dashboards/postgresql/Jenkins.json | 20 +-- grafana/dashboards/postgresql/Jira.json | 22 ++-- grafana/dashboards/postgresql/Opsgenie.json | 26 ++-- grafana/dashboards/postgresql/Sonarqube.json | 24 ++-- grafana/dashboards/postgresql/TAPD.json | 22 ++-- grafana/dashboards/postgresql/Taiga.json | 52 ++++---- grafana/dashboards/postgresql/Teambition.json | 22 ++-- grafana/dashboards/postgresql/Testmo.json | 28 ++--- grafana/dashboards/postgresql/Zentao.json | 22 ++-- grafana/dashboards/postgresql/argo-cd.json | 4 +- .../dashboards/postgresql/azure-dev-ops.json | 36 +++--- grafana/dashboards/postgresql/bit-bucket.json | 36 +++--- grafana/dashboards/postgresql/circle-ci.json | 20 +-- .../component-and-file-level-metrics.json | 20 +-- .../postgresql/contributor-experience.json | 16 +-- .../demo-commit-count-by-author.json | 4 +- ...-this-month-more-productive-than-last.json | 2 +- .../demo-was-our-quality-improved-or-not.json | 2 +- .../dashboards/postgresql/dora-by-team.json | 18 +-- grafana/dashboards/postgresql/dora-debug.json | 60 ++++----- .../dora-details-change-failure-rate.json | 8 +- .../dora-details-deployment-frequency.json | 16 +-- ...tails-failed-deployment-recovery-time.json | 6 +- .../dora-details-lead-timefor-changes.json | 12 +- .../dora-details-timeto-restore-service.json | 6 +- .../postgresql/engineering-overview.json | 36 +++--- ...g-throughput-and-cycle-time-team-view.json | 36 +++--- ...engineering-throughput-and-cycle-time.json | 24 ++-- grafana/dashboards/postgresql/git-hub.json | 56 ++++----- ...ase-quality-and-contribution-analysis.json | 44 +++---- grafana/dashboards/postgresql/pager-duty.json | 22 ++-- grafana/dashboards/postgresql/q-dev-dora.json | 14 +-- .../postgresql/sonar-qube-cloud.json | 32 ++--- .../postgresql/weekly-bug-retro.json | 30 ++--- .../postgresql/weekly-community-retro.json | 38 +++--- grafana/dashboards/postgresql/work-logs.json | 28 ++--- .../scripts/convert-mysql-to-postgresql.py | 68 +++++++--- grafana/scripts/entrypoint.sh | 118 +++++++++++++----- 43 files changed, 621 insertions(+), 527 deletions(-) diff --git a/grafana/dashboards/mysql/dora-debug.json b/grafana/dashboards/mysql/dora-debug.json index 1fc487e930e..89935cbc360 100644 --- a/grafana/dashboards/mysql/dora-debug.json +++ b/grafana/dashboards/mysql/dora-debug.json @@ -198,7 +198,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT\n\tpm.project_name,\n\tIF(pm.project_name in ($project),'This project is selected','Not Selected') as select_status,\n\tIF(cdc._raw_data_table != '', cdc._raw_data_table, cdc.cicd_scope_id) as _raw_data_table,\n\tresult,\n\tenvironment,\n\tcount(distinct cdc.id) as deployment_commit_count, \n\tcount(distinct cdc.cicd_deployment_id) as deployment_count\nFROM cicd_deployment_commits cdc\nLEFT join project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\nWHERE $__timeFilter(cdc.finished_date)\nGROUP BY pm.project_name, select_status, _raw_data_table,result,environment", + "rawSql": "SELECT\n\tpm.project_name,\n\tIF(pm.project_name in ($project),'This project is selected','Not Selected') as select_status,\n\tIF(cdc._raw_data_table != '', cdc._raw_data_table, cdc.cicd_scope_id) as _raw_data_table,\n\tresult,\n\tenvironment,\n\tcount(distinct cdc.id) as deployment_commit_count, \n\tcount(distinct cdc.cicd_deployment_id) as deployment_count\nFROM cicd_deployment_commits cdc\nLEFT join project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\nWHERE $__timeFilter(cdc.finished_date)\nGROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/Asana.json b/grafana/dashboards/postgresql/Asana.json index 9d6346da5c1..82eb837507a 100644 --- a/grafana/dashboards/postgresql/Asana.json +++ b/grafana/dashboards/postgresql/Asana.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1142,14 +1142,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'asana%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'asana%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'asana%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'asana%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Bamboo.json b/grafana/dashboards/postgresql/Bamboo.json index 64c97e6a0ac..884604b2612 100644 --- a/grafana/dashboards/postgresql/Bamboo.json +++ b/grafana/dashboards/postgresql/Bamboo.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -511,7 +511,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -649,7 +649,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -806,7 +806,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -973,7 +973,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1109,7 +1109,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%bamboo%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${plan_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${plan_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%bamboo%' AND ('${plan_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${plan_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1190,14 +1190,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'bamboo%'", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'bamboo%'", "hide": 0, "includeAll": true, "label": "Plan Name", "multi": true, "name": "plan_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'bamboo%'", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'bamboo%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/DORA.json b/grafana/dashboards/postgresql/DORA.json index 56fd6a8799a..7ef90d82621 100644 --- a/grafana/dashboards/postgresql/DORA.json +++ b/grafana/dashboards/postgresql/DORA.json @@ -233,7 +233,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, REPLACE(metric, ' ', '-') AS metric_hidden, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", "refId": "A", "select": [ [ @@ -380,7 +380,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -526,7 +526,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -673,7 +673,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'No data' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'No data' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", "refId": "A", "select": [ [ @@ -827,7 +827,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -962,7 +962,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1095,7 +1095,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hours\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hours\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1249,7 +1249,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1407,7 +1407,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/Gitlab.json b/grafana/dashboards/postgresql/Gitlab.json index 88412083cb6..46435b4756d 100644 --- a/grafana/dashboards/postgresql/Gitlab.json +++ b/grafana/dashboards/postgresql/Gitlab.json @@ -150,7 +150,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -300,7 +300,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -439,7 +439,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -566,7 +566,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -728,7 +728,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"MR: Open\", closed AS \"MR: Closed without merging\", merged AS \"MR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"MR: Open\", closed AS \"MR: Closed without merging\", merged AS \"MR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -832,7 +832,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -969,7 +969,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -1076,7 +1076,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1198,7 +1198,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1312,7 +1312,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1419,7 +1419,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1587,7 +1587,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1774,7 +1774,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1915,7 +1915,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2016,7 +2016,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2171,7 +2171,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%gitlab%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%gitlab%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2271,14 +2271,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'gitlab%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'gitlab%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'gitlab%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'gitlab%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Jenkins.json b/grafana/dashboards/postgresql/Jenkins.json index 9fce9549e64..03b70ea1c35 100644 --- a/grafana/dashboards/postgresql/Jenkins.json +++ b/grafana/dashboards/postgresql/Jenkins.json @@ -120,7 +120,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -223,7 +223,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -410,7 +410,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -510,7 +510,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -631,7 +631,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Build Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -788,7 +788,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Build Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -955,7 +955,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_build_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1", "refId": "A", "select": [ [ @@ -1091,7 +1091,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%jenkins%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${job_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${job_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%jenkins%' AND ('${job_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${job_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1173,14 +1173,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'jenkins%'", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'jenkins%'", "hide": 0, "includeAll": true, "label": "Job Name", "multi": true, "name": "job_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'jenkins%'", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'jenkins%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Jira.json b/grafana/dashboards/postgresql/Jira.json index d95e8e6cf06..2a8ee99de36 100644 --- a/grafana/dashboards/postgresql/Jira.json +++ b/grafana/dashboards/postgresql/Jira.json @@ -177,7 +177,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -282,7 +282,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -420,7 +420,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -511,7 +511,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -630,7 +630,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -744,7 +744,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -849,7 +849,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -972,7 +972,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1090,7 +1090,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1197,14 +1197,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'jira%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'jira%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'jira%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'jira%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Opsgenie.json b/grafana/dashboards/postgresql/Opsgenie.json index 1d12c5ae203..c4c75b0d282 100644 --- a/grafana/dashboards/postgresql/Opsgenie.json +++ b/grafana/dashboards/postgresql/Opsgenie.json @@ -177,7 +177,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -280,7 +280,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -412,7 +412,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.title AS \"Title\", i.issue_key AS \"Id\", STRING_AGG(b.name, ', ') AS \"Service(s)\", i.description AS \"Description\", i.original_status AS \"Original Status\", i.priority AS \"Priority\", i.created_date AS \"Created Date\", i.updated_date AS \"Updated Date\", ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Lead Time Days\", i.url AS \"URL\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY i.title, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, i.lead_time_minutes, i.url", + "rawSql": "SELECT i.title AS \"Title\", i.issue_key AS \"Id\", STRING_AGG(b.name, ', ') AS \"Service(s)\", i.description AS \"Description\", i.original_status AS \"Original Status\", i.priority AS \"Priority\", i.created_date AS \"Created Date\", i.updated_date AS \"Updated Date\", ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Lead Time Days\", i.url AS \"URL\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY i.title, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, i.lead_time_minutes, i.url", "refId": "A", "select": [ [ @@ -515,7 +515,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'closed' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.issue_key) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'closed' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -622,7 +622,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -756,7 +756,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -890,7 +890,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -999,7 +999,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -1138,7 +1138,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1240,7 +1240,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1465,7 +1465,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT i.title AS \"Incident Name\", '[' || STRING_AGG('{\"name\": \"' || a.assignee_name || '\"' || ', \"type\": \"' || r.type || '\"}', ', ') || ']' AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (REVERSE(SPLIT_PART(REVERSE(i.id), ':', 1)) = REVERSE(SPLIT_PART(REVERSE(a.issue_id), ':', 1))) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id LIKE 'opsgenie%' GROUP BY i.title", + "rawSql": "SELECT i.title AS \"Incident Name\", '[' || STRING_AGG('{\"name\": \"' || a.assignee_name || '\"' || ', \"type\": \"' || r.type || '\"}', ', ') || ']' AS \"Responders\" FROM issues AS i INNER JOIN issue_assignees AS a ON (REVERSE(SPLIT_PART(REVERSE(i.id), ':', 1)) = REVERSE(SPLIT_PART(REVERSE(a.issue_id), ':', 1))) INNER JOIN _tool_opsgenie_responders AS r ON a.assignee_id = r.id WHERE i.id::text LIKE 'opsgenie%' GROUP BY i.title", "refId": "A", "sql": { "columns": [ @@ -1507,14 +1507,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'opsgenie%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'opsgenie%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'opsgenie%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'opsgenie%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Sonarqube.json b/grafana/dashboards/postgresql/Sonarqube.json index 91d05aa1f8f..02d9ccbc250 100644 --- a/grafana/dashboards/postgresql/Sonarqube.json +++ b/grafana/dashboards/postgresql/Sonarqube.json @@ -151,7 +151,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Bugs\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'BUG' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Bugs\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'BUG' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -239,7 +239,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Vulnerabilities\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'VULNERABILITY' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Vulnerabilities\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'VULNERABILITY' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -328,7 +328,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'HOTSPOTS' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -416,7 +416,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'HOTSPOTS' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -530,7 +530,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -619,7 +619,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'CODE_SMELL' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -708,7 +708,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'CODE_SMELL' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -822,7 +822,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -910,7 +910,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1000,7 +1000,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ @@ -1069,14 +1069,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", + "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id::text LIKE 'sonar%'", "hide": 0, "includeAll": true, "label": "Severity", "multi": true, "name": "severity", "options": [], - "query": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", + "query": "SELECT DISTINCT severity FROM cq_issues WHERE id::text LIKE 'sonar%'", "refresh": 1, "regex": "", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/TAPD.json b/grafana/dashboards/postgresql/TAPD.json index eddd05e9888..61f2aa358dd 100644 --- a/grafana/dashboards/postgresql/TAPD.json +++ b/grafana/dashboards/postgresql/TAPD.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'tapd%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'tapd%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'tapd%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'tapd%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Taiga.json b/grafana/dashboards/postgresql/Taiga.json index bdd2e66b3c7..336d855d2c0 100644 --- a/grafana/dashboards/postgresql/Taiga.json +++ b/grafana/dashboards/postgresql/Taiga.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Stories\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Closed Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Stories\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Closed Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -813,7 +813,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -928,7 +928,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1042,7 +1042,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1173,7 +1173,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1258,7 +1258,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.status = 'DONE'", + "rawSql": "SELECT SUM(ts.total_points) AS value FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.status = 'DONE'", "refId": "A", "sql": { "columns": [ @@ -1369,7 +1369,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", SUM(ts.total_points) AS \"Story Points\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", SUM(ts.total_points) AS \"Story Points\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1473,7 +1473,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", SUM(i.story_point) AS \"Story Points\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", SUM(i.story_point) AS \"Story Points\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY \"Story Points\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1558,7 +1558,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT SUM(ts.total_points) AS \"Story Points\", CASE WHEN i.status = 'DONE' THEN 'Completed' ELSE 'In Progress' END AS \"Status\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"Status\"", + "rawSql": "SELECT SUM(ts.total_points) AS \"Story Points\", CASE WHEN i.status = 'DONE' THEN 'Completed' ELSE 'In Progress' END AS \"Status\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY \"Status\"", "refId": "A", "select": [ [ @@ -1666,7 +1666,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS \"To Do\", COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS \"In Progress\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS \"Done\", COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS \"Other\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.status = 'TODO' THEN i.id END) AS \"To Do\", COUNT(DISTINCT CASE WHEN i.status = 'IN_PROGRESS' THEN i.id END) AS \"In Progress\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id END) AS \"Done\", COUNT(DISTINCT CASE WHEN NOT i.status IN ('TODO', 'IN_PROGRESS', 'DONE') THEN i.id END) AS \"Other\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A" } ], @@ -1751,7 +1751,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", COUNT(DISTINCT i.id) AS \"User Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(i.assignee_name, ''), 'Unassigned') AS \"Assignee\", COUNT(DISTINCT i.id) AS \"User Stories\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1854,7 +1854,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT ts.user_story_id) AS \"User Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT ts.user_story_id) AS \"User Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY \"User Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1956,7 +1956,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Open Stories\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Closed Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY \"Closed Stories\" DESC NULLS LAST", + "rawSql": "SELECT COALESCE(NULLIF(ts.milestone_name, ''), 'No Milestone') AS \"Milestone\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Open Stories\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN ts.user_story_id ELSE NULL END) AS \"Closed Stories\" FROM _tool_taiga_user_stories AS ts JOIN board_issues AS bi ON bi.issue_id = 'taiga:TaigaUserStory:' || ts.connection_id || ':' || ts.user_story_id JOIN issues AS i ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY \"Closed Stories\" DESC NULLS LAST", "refId": "A", "select": [ [ @@ -2098,7 +2098,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Story\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Task\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issue\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT NOW() AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Story\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Task\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issue\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A" } ], @@ -2182,7 +2182,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN i.id ELSE NULL END) AS \"Open\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Closed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", COUNT(DISTINCT CASE WHEN i.status <> 'DONE' THEN i.id ELSE NULL END) AS \"Open\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Closed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -2272,7 +2272,7 @@ "datasource": "postgresql", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Stories\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Tasks\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issues\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epics\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN i.type = 'USER_STORY' THEN i.id END) AS \"User Stories\", COUNT(DISTINCT CASE WHEN i.type = 'TASK' THEN i.id END) AS \"Tasks\", COUNT(DISTINCT CASE WHEN i.type = 'REQUIREMENT' THEN i.id END) AS \"Issues\", COUNT(DISTINCT CASE WHEN i.type = 'EPIC' THEN i.id END) AS \"Epics\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND $__timeFilter(i.created_date) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A" } ], @@ -2361,7 +2361,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", ROUND(CAST(AVG(CAST(COALESCE(i.lead_time_minutes, (EXTRACT(EPOCH FROM (i.resolution_date - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Avg Lead Time (days)\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT COALESCE(i.original_type, i.type) AS \"Issue Type\", ROUND(CAST(AVG(CAST(COALESCE(i.lead_time_minutes, (EXTRACT(EPOCH FROM (i.resolution_date - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS \"Avg Lead Time (days)\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.status = 'DONE' AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A" } ], @@ -2388,14 +2388,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'taiga%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'taiga%'", "hide": 0, "includeAll": true, "label": "Choose Project", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'taiga%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'taiga%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, @@ -2413,14 +2413,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id LIKE 'taiga%'", + "definition": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text LIKE 'taiga%'", "hide": 0, "includeAll": true, "label": "Issue Type", "multi": true, "name": "type", "options": [], - "query": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id LIKE 'taiga%'", + "query": "SELECT DISTINCT i.type FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE bi.board_id::text LIKE 'taiga%'", "refresh": 1, "regex": "", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Teambition.json b/grafana/dashboards/postgresql/Teambition.json index b4113d7a5f6..8fc905b4157 100644 --- a/grafana/dashboards/postgresql/Teambition.json +++ b/grafana/dashboards/postgresql/Teambition.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'teambition%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'teambition%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'teambition%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'teambition%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Testmo.json b/grafana/dashboards/postgresql/Testmo.json index 0d7401a8ab3..9e8bcce9a65 100644 --- a/grafana/dashboards/postgresql/Testmo.json +++ b/grafana/dashboards/postgresql/Testmo.json @@ -168,7 +168,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(*) AS value FROM (SELECT DISTINCT ar.id, ar.connection_id, ar.testmo_created_at FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT DISTINCT r.id, r.connection_id, r.testmo_created_at FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(*) AS value FROM (SELECT DISTINCT ar.id, ar.connection_id, ar.testmo_created_at FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT DISTINCT r.id, r.connection_id, r.testmo_created_at FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "refId": "A" } ], @@ -237,7 +237,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS value FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "rawSql": "SELECT CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS value FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "refId": "A" } ], @@ -372,7 +372,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, COUNT(CASE WHEN combined.status = 1 THEN 1 ELSE NULL END) AS \"Passed Tests\", COUNT(CASE WHEN combined.status = 2 THEN 1 ELSE NULL END) AS \"Failed Tests\", COUNT(CASE WHEN NOT combined.status IN (1, 2) THEN 1 ELSE NULL END) AS \"Other Tests\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, COUNT(CASE WHEN combined.status = 1 THEN 1 ELSE NULL END) AS \"Passed Tests\", COUNT(CASE WHEN combined.status = 2 THEN 1 ELSE NULL END) AS \"Failed Tests\", COUNT(CASE WHEN NOT combined.status IN (1, 2) THEN 1 ELSE NULL END) AS \"Other Tests\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -431,7 +431,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT r.id) AS value FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT r.id) AS value FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND ('${connection_id:csv}' = '' OR r.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "refId": "A" } ], @@ -500,7 +500,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ar.id) AS value FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT ar.id) AS value FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ('${connection_id:csv}' = '' OR ar.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "refId": "A" } ], @@ -605,7 +605,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(*) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(*) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND ('${connection_id:csv}' = '' OR r.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -689,7 +689,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT p.name AS metric, COUNT(*) AS \"Test Runs\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY p.name ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT p.name AS metric, COUNT(*) AS \"Test Runs\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY p.name ORDER BY COUNT(*) DESC NULLS LAST LIMIT 10", "refId": "A" } ], @@ -802,7 +802,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Success Rate\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(combined.testmo_created_at AS DATE) AS time, CAST(COUNT(CASE WHEN combined.status IN (1, 2) THEN 1 ELSE NULL END) * 1.0 AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Success Rate\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.status FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.status FROM _tool_testmo_runs AS r) AS combined WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(combined.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -887,7 +887,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Test Count\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Test Count\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ('${connection_id:csv}' = '' OR ar.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1006,7 +1006,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Automation Runs\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ar.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(ar.testmo_created_at AS DATE) AS time, COUNT(DISTINCT ar.id) AS \"Automation Runs\" FROM _tool_testmo_automation_runs AS ar WHERE $__timeFilter(ar.testmo_created_at) AND ('${connection_id:csv}' = '' OR ar.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(ar.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "A" }, { @@ -1015,7 +1015,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(DISTINCT r.id) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND r.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(r.testmo_created_at AS DATE) AS time, COUNT(DISTINCT r.id) AS \"Test Runs\" FROM _tool_testmo_runs AS r WHERE $__timeFilter(r.testmo_created_at) AND ('${connection_id:csv}' = '' OR r.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) GROUP BY CAST(r.testmo_created_at AS DATE) ORDER BY time NULLS FIRST", "refId": "B" } ], @@ -1121,7 +1121,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT combined.id AS \"Test Run ID\", p.name AS \"Project\", combined.name AS \"Test Name\", combined.status_name AS \"Status\", combined.testmo_created_at AS \"Created Date\", combined.source AS \"Source\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id, ar.name, CASE WHEN ar.status = 1 THEN 'Failed' WHEN ar.status = 2 THEN 'Passed' ELSE 'Other' END AS status_name, 'Automation Run' AS source FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id, r.name, r.status_name, 'Test Run' AS source FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND combined.connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v) ORDER BY combined.testmo_created_at DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT combined.id AS \"Test Run ID\", p.name AS \"Project\", combined.name AS \"Test Name\", combined.status_name AS \"Status\", combined.testmo_created_at AS \"Created Date\", combined.source AS \"Source\" FROM (SELECT ar.id, ar.connection_id, ar.testmo_created_at, ar.project_id, ar.name, CASE WHEN ar.status = 1 THEN 'Failed' WHEN ar.status = 2 THEN 'Passed' ELSE 'Other' END AS status_name, 'Automation Run' AS source FROM _tool_testmo_automation_runs AS ar UNION ALL SELECT r.id, r.connection_id, r.testmo_created_at, r.project_id, r.name, r.status_name, 'Test Run' AS source FROM _tool_testmo_runs AS r) AS combined JOIN _tool_testmo_projects AS p ON combined.project_id = p.id AND combined.connection_id = p.connection_id WHERE $__timeFilter(combined.testmo_created_at) AND ('${connection_id:csv}' = '' OR combined.connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[])) ORDER BY combined.testmo_created_at DESC NULLS LAST LIMIT 20", "refId": "A" } ], @@ -1203,14 +1203,14 @@ "value": "$__all" }, "datasource": "postgresql", - "definition": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "definition": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE ('${connection_id:csv}' = '' OR connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "hide": 0, "includeAll": true, "label": "Project", "multi": true, "name": "project", "options": [], - "query": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE connection_id::text IN (SELECT v FROM unnest(CASE WHEN '${connection_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${connection_id}]::text[] END) v)", + "query": "SELECT DISTINCT name FROM _tool_testmo_projects WHERE ('${connection_id:csv}' = '' OR connection_id::text = ANY(ARRAY[${connection_id:singlequote}]::text[]))", "refresh": 1, "regex": "", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/Zentao.json b/grafana/dashboards/postgresql/Zentao.json index 18074b4da59..b99b10e1861 100644 --- a/grafana/dashboards/postgresql/Zentao.json +++ b/grafana/dashboards/postgresql/Zentao.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -413,7 +413,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS \"Number of Open Issues\", COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS \"Number of Delivered Issues\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS delivered_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * delivered_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -618,7 +618,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS delivered_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, delivered_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -729,7 +729,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -815,7 +815,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -936,7 +936,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_lead_time FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1145,14 +1145,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'zentao%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'zentao%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'zentao%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'zentao%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/argo-cd.json b/grafana/dashboards/postgresql/argo-cd.json index 9dec8ebabf0..6ace7a63d50 100644 --- a/grafana/dashboards/postgresql/argo-cd.json +++ b/grafana/dashboards/postgresql/argo-cd.json @@ -1004,14 +1004,14 @@ }, "datasource": "postgresql", "allValue": "$__all", - "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'argocd:ArgocdApplication:%'", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'argocd:ArgocdApplication:%'", "hide": 0, "includeAll": true, "label": "Application", "multi": true, "name": "application_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE 'argocd:ArgocdApplication:%'", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE 'argocd:ArgocdApplication:%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/azure-dev-ops.json b/grafana/dashboards/postgresql/azure-dev-ops.json index 5b0f3746cf8..9070b3a25c7 100644 --- a/grafana/dashboards/postgresql/azure-dev-ops.json +++ b/grafana/dashboards/postgresql/azure-dev-ops.json @@ -151,7 +151,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -301,7 +301,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -440,7 +440,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -572,7 +572,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -1083,7 +1083,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1205,7 +1205,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1319,7 +1319,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1426,7 +1426,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1594,7 +1594,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1781,7 +1781,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id::text LIKE '%azure%' */ ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2023,7 +2023,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id LIKE '%azure%' */ cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND /* and id::text LIKE '%azure%' */ ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2278,14 +2278,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'azure%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'azure%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'azure%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'azure%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/bit-bucket.json b/grafana/dashboards/postgresql/bit-bucket.json index 1e658b09f07..e71df2063bc 100644 --- a/grafana/dashboards/postgresql/bit-bucket.json +++ b/grafana/dashboards/postgresql/bit-bucket.json @@ -154,7 +154,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -304,7 +304,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -443,7 +443,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -575,7 +575,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -734,7 +734,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -838,7 +838,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -975,7 +975,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('MERGED', 'CLOSED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -1083,7 +1083,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -1205,7 +1205,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1319,7 +1319,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */", "refId": "A", "select": [ [ @@ -1426,7 +1426,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1594,7 +1594,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_count\", failed_count FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1781,7 +1781,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1922,7 +1922,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Pipeline Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2023,7 +2023,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* Enable the following condition to remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2178,7 +2178,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE 'bitbucket%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE 'bitbucket%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2278,14 +2278,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'bitbucket%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'bitbucket%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'bitbucket%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'bitbucket%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/circle-ci.json b/grafana/dashboards/postgresql/circle-ci.json index 39c0ce38636..45cf2881ca1 100644 --- a/grafana/dashboards/postgresql/circle-ci.json +++ b/grafana/dashboards/postgresql/circle-ci.json @@ -122,7 +122,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -227,7 +227,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -415,7 +415,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[])) GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -658,7 +658,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, build_count AS \"Workflow Count\" FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -817,7 +817,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[])) GROUP BY \"time\", \"result\", id) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Success Rate\" FROM _build_success_rate GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -988,7 +988,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -1144,7 +1144,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%circleci%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${full_name}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${full_name}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(finished_date AS DATE)) + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%circleci%' AND ('${full_name:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${full_name:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, CAST(mean_duration_sec AS NUMERIC) / NULLIF(60, 0) AS mean_duration_minutes FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1242,14 +1242,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE '%circleci%'", + "definition": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE '%circleci%'", "hide": 0, "includeAll": true, "label": "CircleCI Project", "multi": true, "name": "full_name", "options": [], - "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id LIKE '%circleci%'", + "query": "SELECT name || '--' || id AS text FROM cicd_scopes WHERE id::text LIKE '%circleci%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/component-and-file-level-metrics.json b/grafana/dashboards/postgresql/component-and-file-level-metrics.json index 7b951305044..f8f0bf89aa2 100644 --- a/grafana/dashboards/postgresql/component-and-file-level-metrics.json +++ b/grafana/dashboards/postgresql/component-and-file-level-metrics.json @@ -137,7 +137,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY commit_nums DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT author_name, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND ('${repo_id:csv}' = '' OR repo_commits.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY commit_nums DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -253,7 +253,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT author_name, SUM(additions - deletions) AS cnt FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT author_name, SUM(additions - deletions) AS cnt FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND ('${repo_id:csv}' = '' OR repo_commits.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(commits.authored_date) GROUP BY \"author_name\", \"author_id\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -395,7 +395,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE $__timeFilter(commits.authored_date) AND commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY \"weekday\"", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, COUNT(DISTINCT commit_sha) AS commit_nums FROM commits, repo_commits WHERE $__timeFilter(commits.authored_date) AND commits.sha = repo_commits.commit_sha AND ('${repo_id:csv}' = '' OR repo_commits.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY \"weekday\"", "refId": "A", "select": [ [ @@ -528,7 +528,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, SUM(additions - deletions) AS changed_nums, SUM(additions) AS total_additions, SUM(deletions) AS total_deletions FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND repo_commits.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.authored_date) GROUP BY \"weekday\" ORDER BY weekday NULLS FIRST", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(authored_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS weekday, SUM(additions - deletions) AS changed_nums, SUM(additions) AS total_additions, SUM(deletions) AS total_deletions FROM commits, repo_commits WHERE commits.sha = repo_commits.commit_sha AND ('${repo_id:csv}' = '' OR repo_commits.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(commits.authored_date) GROUP BY \"weekday\" ORDER BY weekday NULLS FIRST", "refId": "A", "select": [ [ @@ -687,7 +687,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT c.author_name) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT c.author_name) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE ('${repo_id:csv}' = '' OR repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -789,7 +789,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT author_name) AS author_count, MAX(rst) AS lines_of_code, CAST(MAX(rst) AS NUMERIC) / NULLIF(COUNT(DISTINCT author_name), 0) AS rate FROM commits JOIN (SELECT file_path, commit_files.commit_sha, SUM(additions - deletions) AS rst FROM commit_files JOIN repo_commits AS rc ON commit_files.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\", commit_files.commit_sha) AS a ON a.commit_sha = commits.sha GROUP BY file_path HAVING author_count > 0 ORDER BY rate DESC NULLS LAST", + "rawSql": "SELECT file_path, COUNT(DISTINCT author_name) AS author_count, MAX(rst) AS lines_of_code, CAST(MAX(rst) AS NUMERIC) / NULLIF(COUNT(DISTINCT author_name), 0) AS rate FROM commits JOIN (SELECT file_path, commit_files.commit_sha, SUM(additions - deletions) AS rst FROM commit_files JOIN repo_commits AS rc ON commit_files.commit_sha = rc.commit_sha WHERE ('${repo_id:csv}' = '' OR repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\", commit_files.commit_sha) AS a ON a.commit_sha = commits.sha GROUP BY file_path HAVING author_count > 0 ORDER BY rate DESC NULLS LAST", "refId": "A", "select": [ [ @@ -920,7 +920,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS modified_num FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY modified_num DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS modified_num FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE ('${repo_id:csv}' = '' OR repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(c.authored_date) AND CAST(file_path AS TEXT) ~ '${selected_path:raw}' GROUP BY \"file_path\" ORDER BY modified_num DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1054,7 +1054,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(c.authored_date) GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT file_path, COUNT(DISTINCT sha) AS cnt FROM commits AS c JOIN commit_files AS cf ON cf.commit_sha = c.sha JOIN repo_commits AS rc ON cf.commit_sha = rc.commit_sha WHERE ('${repo_id:csv}' = '' OR repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(c.authored_date) GROUP BY \"file_path\" ORDER BY cnt DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1211,7 +1211,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(commits.committed_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS wd, COUNT(*) AS lived_lines FROM repo_snapshot JOIN commits ON commits.sha = repo_snapshot.commit_sha WHERE repo_snapshot.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.committed_date) GROUP BY \"wd\" ORDER BY wd NULLS FIRST", + "rawSql": "SELECT CASE CAST(EXTRACT(ISODOW FROM CAST(commits.committed_date AS DATE)) AS TEXT) WHEN '2' THEN '1.\"Monday\"' WHEN '3' THEN '2.\"Tuesday\"' WHEN '4' THEN '3.\"Wednesday\"' WHEN '5' THEN '4.\"Thursday\"' WHEN '6' THEN '5.\"Friday\"' WHEN '7' THEN '6.\"Saturday\"' WHEN '1' THEN '7.\"Sunday\"' END AS wd, COUNT(*) AS lived_lines FROM repo_snapshot JOIN commits ON commits.sha = repo_snapshot.commit_sha WHERE ('${repo_id:csv}' = '' OR repo_snapshot.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(commits.committed_date) GROUP BY \"wd\" ORDER BY wd NULLS FIRST", "refId": "A", "select": [ [ @@ -1316,7 +1316,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT file_path, AVG((EXTRACT(EPOCH FROM (NOW() - commits.committed_date))/86400)) AS line_age FROM repo_snapshot JOIN commits ON repo_snapshot.commit_sha = commits.sha WHERE repo_snapshot.repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(commits.committed_date) GROUP BY \"file_path\" ORDER BY line_age DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_path, AVG((EXTRACT(EPOCH FROM (NOW() - commits.committed_date))/86400)) AS line_age FROM repo_snapshot JOIN commits ON repo_snapshot.commit_sha = commits.sha WHERE ('${repo_id:csv}' = '' OR repo_snapshot.repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(commits.committed_date) GROUP BY \"file_path\" ORDER BY line_age DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/contributor-experience.json b/grafana/dashboards/postgresql/contributor-experience.json index 0651391fae3..e4e417f39d9 100644 --- a/grafana/dashboards/postgresql/contributor-experience.json +++ b/grafana/dashboards/postgresql/contributor-experience.json @@ -117,7 +117,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -201,7 +201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND i.status = 'DONE' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -285,7 +285,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT CAST(100 * SUM(CASE WHEN CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(60, 0) < $iir_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))) SELECT CAST(100 * SUM(CASE WHEN CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(60, 0) < $iir_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -369,7 +369,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = '$label_gfi' AND i.status <> 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN issue_labels AS il ON il.issue_id = i.id WHERE il.label_name = '$label_gfi' AND i.status <> 'DONE' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -470,7 +470,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH pr_comment_list AS (SELECT pr.id AS issue_id, pr.url, pr.title, pr.created_date AS pr_created_date, prc.id AS comment_id, prc.created_date AS comment_date, prc.account_id, CASE WHEN NOT prc.id IS NULL THEN RANK() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE CAST(pr.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - pr_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM pr_comment_list WHERE comment_rank = 1", + "rawSql": "WITH pr_comment_list AS (SELECT pr.id AS issue_id, pr.url, pr.title, pr.created_date AS pr_created_date, prc.id AS comment_id, prc.created_date AS comment_date, prc.account_id, CASE WHEN NOT prc.id IS NULL THEN RANK() OVER (PARTITION BY pr.id ORDER BY prc.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id WHERE CAST(pr.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR pr.base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - pr_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM pr_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -554,7 +554,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND ('${repo_id:csv}' = '' OR pr.base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -639,7 +639,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * SUM(CASE WHEN CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0) < $prrt_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * SUM(CASE WHEN CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0) < $prrt_sla THEN 1 ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND status IN ('CLOSED', 'MERGED') AND ('${repo_id:csv}' = '' OR pr.base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -747,7 +747,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND pr.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(100 * COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' - INTERVAL '1 MONTH' AND CURRENT_DATE - INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR pr.base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/demo-commit-count-by-author.json b/grafana/dashboards/postgresql/demo-commit-count-by-author.json index bda60d9e20f..826fc07bddc 100644 --- a/grafana/dashboards/postgresql/demo-commit-count-by-author.json +++ b/grafana/dashboards/postgresql/demo-commit-count-by-author.json @@ -112,7 +112,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE c.message::text NOT LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -187,7 +187,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE NOT c.message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", + "rawSql": "WITH commit_data AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, c.author_name, COUNT(*) AS commit_count FROM commits AS c WHERE c.message::text NOT LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 2, 1 ORDER BY 2 NULLS FIRST, 1 NULLS FIRST), this_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) AS this_month), last_month AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-1 MONTH' AS last_month), the_month_before_last AS (SELECT CAST(CURRENT_DATE AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(CURRENT_DATE AS DATE)) + 1) + INTERVAL '-2 MONTH' AS the_month_before_last), this_month_record AS (SELECT author_name, commit_count AS this_month_count, time AS this_month FROM commit_data WHERE time IN (SELECT this_month FROM this_month)), last_month_record AS (SELECT author_name, commit_count AS last_month_count, time AS last_month FROM commit_data WHERE time IN (SELECT last_month FROM last_month)), the_month_before_last_record AS (SELECT author_name, commit_count AS the_month_before_last_count, time AS the_month_before_last FROM commit_data WHERE time IN (SELECT the_month_before_last FROM the_month_before_last)) SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr RIGHT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name RIGHT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name UNION SELECT COALESCE(NULLIF(tmr.author_name, ''), NULLIF(lmr.author_name, ''), tmblr.author_name) AS \"Author Name\", COALESCE(tmblr.the_month_before_last_count, 0) AS \"The Month before Last\", COALESCE(lmr.last_month_count, 0) AS \"Last Month\", COALESCE(tmr.this_month_count, 0) AS \"This Month\", CASE WHEN lmr.last_month_count IS NULL OR tmr.this_month_count IS NULL THEN '-' ELSE ROUND(CAST(100 * (tmr.this_month_count - lmr.last_month_count) AS NUMERIC) / NULLIF(lmr.last_month_count, 0), 1)::TEXT || '%' END AS \"Changes in last 2 month\" FROM this_month_record AS tmr LEFT JOIN last_month_record AS lmr ON tmr.author_name = lmr.author_name LEFT JOIN the_month_before_last_record AS tmblr ON lmr.author_name = tmblr.author_name ORDER BY 4 DESC NULLS LAST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json b/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json index af34787844e..55fd923acdf 100644 --- a/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json +++ b/grafana/dashboards/postgresql/demo-is-this-month-more-productive-than-last.json @@ -138,7 +138,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _commits AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, COUNT(*) AS commit_count FROM commits WHERE NOT message LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, commit_count AS \"Commit Count\" FROM _commits ORDER BY time NULLS FIRST", + "rawSql": "WITH _commits AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, COUNT(*) AS commit_count FROM commits WHERE message::text NOT LIKE '%Merge%' AND $__timeFilter(authored_date) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, commit_count AS \"Commit Count\" FROM _commits ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json b/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json index 76213fceb35..22fb2464dd2 100644 --- a/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json +++ b/grafana/dashboards/postgresql/demo-was-our-quality-improved-or-not.json @@ -109,7 +109,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, SUM(additions + deletions) AS line_count FROM commits WHERE NOT message LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> '0') SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, bug_count_per_1k_loc AS \"Bug Count per 1000 Lines of Code\" FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", + "rawSql": "WITH line_of_code AS (SELECT CAST(authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(authored_date AS DATE)) + 1) AS time, SUM(additions + deletions) AS line_count FROM commits WHERE message::text NOT LIKE 'Merge%' AND $__timeFilter(authored_date) GROUP BY 1), bug_count AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(*) AS bug_count FROM issues AS i WHERE type = 'BUG' AND $__timeFilter(created_date) GROUP BY 1), bug_count_per_1k_loc AS (SELECT loc.time, CAST(1.0 * bc.bug_count AS NUMERIC) / NULLIF(loc.line_count, 0) * 1000 AS bug_count_per_1k_loc FROM line_of_code AS loc LEFT JOIN bug_count AS bc ON bc.time = loc.time WHERE NOT bc.bug_count IS NULL AND NOT loc.line_count IS NULL AND loc.line_count <> '0') SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, bug_count_per_1k_loc AS \"Bug Count per 1000 Lines of Code\" FROM bug_count_per_1k_loc ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/dora-by-team.json b/grafana/dashboards/postgresql/dora-by-team.json index b5d8addbb24..6072334963b 100644 --- a/grafana/dashboards/postgresql/dora-by-team.json +++ b/grafana/dashboards/postgresql/dora-by-team.json @@ -206,7 +206,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _metric_deployment_frequency AS (SELECT 'Deployment frequency' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS value FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months), _pr_stats /* Metric 2: median lead time for changes */ AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5), _metric_change_lead_time AS (SELECT 'Lead time for changes' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS value FROM _median_change_lead_time), _deployments /* Metric 3: change failure rate */ AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1), _metric_cfr AS (SELECT 'Change failure rate' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS value FROM _change_failure_rate, _is_collected_data), _incidents_for_deployments /* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT 'Failed deployment recovery time' AS metric, CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN 'Less than one hour(elite)' WHEN median_recovery_time < 24 * 60 THEN 'Less than one day(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT 'Time to restore service' AS metric, CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr), _metric_mrt_or_mm AS (SELECT metric, median_recovery_time AS value FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT metric, median_time_to_resolve AS value FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'), _final_results AS (SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m1.metric AS _metric, m1.value FROM dora_benchmarks AS db LEFT JOIN _metric_deployment_frequency AS m1 ON db.metric = m1.metric WHERE NOT m1.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m2.metric AS _metric, m2.value FROM dora_benchmarks AS db LEFT JOIN _metric_change_lead_time AS m2 ON db.metric = m2.metric WHERE NOT m2.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m3.metric AS _metric, m3.value FROM dora_benchmarks AS db LEFT JOIN _metric_cfr AS m3 ON db.metric = m3.metric WHERE NOT m3.metric IS NULL AND db.dora_report = ('$dora_report') UNION SELECT DISTINCT db.id, db.metric, db.low, db.medium, db.high, db.elite, m4.metric AS _metric, m4.value FROM dora_benchmarks AS db LEFT JOIN _metric_mrt_or_mm AS m4 ON db.metric = m4.metric WHERE NOT m4.metric IS NULL AND db.dora_report = ('$dora_report')) SELECT metric, CASE WHEN low = value THEN low ELSE NULL END AS low, CASE WHEN medium = value THEN medium ELSE NULL END AS medium, CASE WHEN high = value THEN high ELSE NULL END AS high, CASE WHEN elite = value THEN elite ELSE NULL END AS elite FROM _final_results ORDER BY id NULLS FIRST", "refId": "A", "sql": { "columns": [ @@ -325,7 +325,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE 0 END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN median_number_of_deployment_days_per_week || ' deployment days per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_month || ' deployment days per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN median_number_of_deployment_days_per_week || ' deployment days per week(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN median_number_of_deployment_days_per_month || ' deployment days per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN median_number_of_deployment_days_per_six_months || ' deployment days per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -462,7 +462,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN (ROUND(CAST(CAST(median_change_lead_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "sql": { "columns": [ @@ -579,7 +579,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 4: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", + "rawSql": "/* Metric 4: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.10 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN ROUND(change_failure_rate * 100, 1) || '%(elite)' WHEN change_failure_rate <= 0.20 THEN ROUND(change_failure_rate * 100, 1) || '%(high)' WHEN change_failure_rate <= 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(medium)' WHEN change_failure_rate > 0.30 THEN ROUND(change_failure_rate * 100, 1) || '%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", "refId": "A", "sql": { "columns": [ @@ -697,7 +697,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'N/A. Please check if you have collected deployments or incidents.' END END AS median_recovery_time FROM _median_recovery_time), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_time_to_resolve < 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN (ROUND(CAST(CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)')::TEXT ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "sql": { "columns": [ @@ -804,7 +804,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE $__timeFilter(month_timestamp)", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -909,7 +909,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -1033,7 +1033,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* Metric 4: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE $__timeFilter(month_timestamp)", + "rawSql": "/* Metric 4: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE $__timeFilter(month_timestamp)", "refId": "A", "sql": { "columns": [ @@ -1143,7 +1143,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE t.name::text IN (SELECT v FROM unnest(CASE WHEN '${team}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN commits AS c ON cdc.commit_sha = c.sha JOIN user_accounts AS ua ON c.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.resolution_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE ('${team:csv}' = '' OR t.name::text = ANY(ARRAY[${team:singlequote}]::text[])) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/dora-debug.json b/grafana/dashboards/postgresql/dora-debug.json index 31088b3013c..d7b56e72191 100644 --- a/grafana/dashboards/postgresql/dora-debug.json +++ b/grafana/dashboards/postgresql/dora-debug.json @@ -198,7 +198,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE $__timeFilter(cdc.finished_date) GROUP BY pm.project_name, \"select_status\", \"_raw_data_table\", \"result\", \"environment\"", + "rawSql": "SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ @@ -320,7 +320,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)' WHEN median_number_of_deployment_days_per_month < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per month(low)' ELSE 'N/A. Please check if you have collected deployments.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_number_of_deployment_days_per_week >= 5 THEN 'On-demand(elite)' WHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)' WHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)' WHEN median_number_of_deployment_days_per_six_months < 1 AND NOT is_collected IS NULL THEN 'Fewer than once per six months(low)' ELSE 'N/A. Please check if you have collected deployments.' END ELSE 'Invalid dora report' END AS \"Deployment Frequency\" FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months", "refId": "A", "select": [ [ @@ -445,7 +445,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "rawSql": "SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ @@ -557,7 +557,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 1: Number of deployments per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, COUNT(cicd_deployment_id) AS deployment_count FROM (SELECT cdc.cicd_deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS _production_deployments GROUP BY 1) SELECT cm.month, CASE WHEN d.deployment_count IS NULL THEN 0 ELSE d.deployment_count END AS \"Deployment Count\" FROM calendar_months AS cm LEFT JOIN _deployments AS d ON cm.month = d.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -690,7 +690,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)", "refId": "A", "select": [ [ @@ -807,7 +807,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1", "refId": "A", "select": [ [ @@ -924,7 +924,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(CAST(day AS TIMESTAMP), 'YY/MM') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)), _deployment_days AS (SELECT DISTINCT CAST(finished_date AS DATE) AS day, COUNT(1) AS deployment_count FROM _deployments GROUP BY 1) SELECT TO_CHAR(CAST(day AS TIMESTAMP), 'YY/MM') AS month, SUM(deployment_count) AS monthly_deployment_counts FROM _deployment_days GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1068,7 +1068,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, pr._raw_data_table, COUNT(*) AS total_number_of_PRs FROM pull_requests AS pr /* \tjoin project_pr_metrics prm on prm.id = pr.id */ JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND /* \tand pr.merged_date is not null */ /* \tand prm.pr_cycle_time is not null */ $__timeFilter(pr.created_date) GROUP BY 1, 2", + "rawSql": "SELECT pm.project_name, pr._raw_data_table, COUNT(*) AS total_number_of_PRs FROM pull_requests AS pr /* \tjoin project_pr_metrics prm on prm.id = pr.id */ JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND /* \tand pr.merged_date is not null */ /* \tand prm.pr_cycle_time is not null */ $__timeFilter(pr.created_date) GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -1189,7 +1189,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", + "rawSql": "/* Metric 2: median lead time for changes */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished in the selected period */ SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_change_lead_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _median_change_lead_time AS (/* use median PR cycle time as the median change lead time */ SELECT MAX(pr_cycle_time) AS median_change_lead_time FROM _median_change_lead_time_ranks WHERE ranks <= 0.5) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN median_change_lead_time < 24 * 60 THEN 'Less than one day(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Between one day and one week(high)' WHEN median_change_lead_time < 30 * 24 * 60 THEN 'Between one week and one month(medium)' WHEN median_change_lead_time >= 30 * 24 * 60 THEN 'More than one month(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN median_change_lead_time < 60 THEN 'Less than one hour(elite)' WHEN median_change_lead_time < 7 * 24 * 60 THEN 'Less than one week(high)' WHEN median_change_lead_time < 180 * 24 * 60 THEN 'Between one week and six months(medium)' WHEN median_change_lead_time >= 180 * 24 * 60 THEN 'More than six months(low)' ELSE 'N/A. Please check if you have collected deployments/pull_requests.' END ELSE 'Invalid dora report' END AS median_change_lead_time FROM _median_change_lead_time", "refId": "A", "select": [ [ @@ -1357,7 +1357,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, pr.title /* pr.status, */, pr.author_name, pr.url, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pr.id /* pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) */ = '$pr_id' AND $__timeFilter(pr.created_date)", + "rawSql": "SELECT pm.project_name, pr.title /* pr.status, */, pr.author_name, pr.url, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pr.id /* ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) */ = '$pr_id' AND $__timeFilter(pr.created_date)", "refId": "A", "select": [ [ @@ -1466,7 +1466,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 2: median change lead time per month */ WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats), _clt AS (SELECT month, MAX(pr_cycle_time) AS median_change_lead_time FROM _find_median_clt_each_month_ranks WHERE ranks <= 0.5 GROUP BY month) SELECT cm.month, CASE WHEN _clt.median_change_lead_time IS NULL THEN 0 ELSE CAST(_clt.median_change_lead_time AS NUMERIC) / NULLIF(60, 0) END AS \"Median Change Lead Time In Hour\" FROM calendar_months AS cm LEFT JOIN _clt ON cm.month = _clt.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -1574,7 +1574,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS \"No. of merged PRs in table.pull_requests\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS \"No. of merged PRs in table.pull_requests\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL", "refId": "A", "select": [ [ @@ -1604,7 +1604,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"No. of PRs in table.project_pr_metrics\" FROM project_pr_metrics WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"No. of PRs in table.project_pr_metrics\" FROM project_pr_metrics WHERE ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "B", "select": [ [ @@ -1711,7 +1711,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", + "rawSql": "SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, result, environment, COUNT(DISTINCT cdc.id) AS deployment_commit_count, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployment_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY 1, 2, 3, 4, 5", "refId": "A", "select": [ [ @@ -1868,7 +1868,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, pr.title /* pr.status, */, pr.url /* \tpr.author_name, */, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE $__timeFilter(pr.created_date) /* pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) */", + "rawSql": "SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, pr.title /* pr.status, */, pr.url /* \tpr.author_name, */, pr.merged_date, pr.created_date FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' WHERE $__timeFilter(pr.created_date) /* ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) */", "refId": "A", "select": [ [ @@ -1967,7 +1967,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* This query can be used to test if the column \"deployment_commit_id\" is associated with the correct PR */ WITH pr_merge_commits AS (SELECT ppm.id AS pr_id, ppm.deployment_commit_id AS id_1, pr.merge_commit_sha, ppm.project_name FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ppm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND ppm.id = '$pr_id'), _deployment_commits AS (SELECT DISTINCT cdc1.id AS id_2, cdc1.prev_success_deployment_commit_id, cdc1.commit_sha AS new_commit_sha, cdc2.commit_sha AS old_commit_sha, cdc1.finished_date, cd.commit_sha AS deployed_commits FROM cicd_deployment_commits AS cdc1 LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc1.prev_success_deployment_commit_id = cdc2.id JOIN commits_diffs AS cd ON cdc1.commit_sha = cd.new_commit_sha AND COALESCE(cdc2.commit_sha, '') = cd.old_commit_sha JOIN project_mapping AS pm ON cdc1.cicd_scope_id = pm.row_id WHERE cdc1.result = 'SUCCESS' AND cdc1.environment = 'PRODUCTION' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)), _find_deployment_commit_id_from_pr AS (SELECT pmc.pr_id, pmc.id_1, pmc.merge_commit_sha, pmc.project_name, dc.id_2, dc.prev_success_deployment_commit_id, dc.deployed_commits, RANK() OVER (PARTITION BY pr_id ORDER BY dc.finished_date NULLS FIRST) AS deployment_rank FROM pr_merge_commits AS pmc LEFT JOIN _deployment_commits AS dc ON pmc.merge_commit_sha = dc.deployed_commits) SELECT pr_id, id_1 AS deployment_commit_id /* If \"id_1\" equals \"id_2\", then pass */, CASE WHEN id_1 = COALESCE(id_2, '') THEN 'YES' ELSE 'NO' END AS if_the_mapping_logic_is_correct /* \tid_2, */ FROM _find_deployment_commit_id_from_pr WHERE deployment_rank = 1", + "rawSql": "/* This query can be used to test if the column \"deployment_commit_id\" is associated with the correct PR */ WITH pr_merge_commits AS (SELECT ppm.id AS pr_id, ppm.deployment_commit_id AS id_1, pr.merge_commit_sha, ppm.project_name FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ('${project:csv}' = '' OR ppm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND ppm.id = '$pr_id'), _deployment_commits AS (SELECT DISTINCT cdc1.id AS id_2, cdc1.prev_success_deployment_commit_id, cdc1.commit_sha AS new_commit_sha, cdc2.commit_sha AS old_commit_sha, cdc1.finished_date, cd.commit_sha AS deployed_commits FROM cicd_deployment_commits AS cdc1 LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc1.prev_success_deployment_commit_id = cdc2.id JOIN commits_diffs AS cd ON cdc1.commit_sha = cd.new_commit_sha AND COALESCE(cdc2.commit_sha, '') = cd.old_commit_sha JOIN project_mapping AS pm ON cdc1.cicd_scope_id = pm.row_id WHERE cdc1.result = 'SUCCESS' AND cdc1.environment = 'PRODUCTION' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))), _find_deployment_commit_id_from_pr AS (SELECT pmc.pr_id, pmc.id_1, pmc.merge_commit_sha, pmc.project_name, dc.id_2, dc.prev_success_deployment_commit_id, dc.deployed_commits, RANK() OVER (PARTITION BY pr_id ORDER BY dc.finished_date NULLS FIRST) AS deployment_rank FROM pr_merge_commits AS pmc LEFT JOIN _deployment_commits AS dc ON pmc.merge_commit_sha = dc.deployed_commits) SELECT pr_id, id_1 AS deployment_commit_id /* If \"id_1\" equals \"id_2\", then pass */, CASE WHEN id_1 = COALESCE(id_2, '') THEN 'YES' ELSE 'NO' END AS if_the_mapping_logic_is_correct /* \tid_2, */ FROM _find_deployment_commit_id_from_pr WHERE deployment_rank = 1", "refId": "A", "select": [ [ @@ -2187,7 +2187,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_pickup_time AS \"PR pickup time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", + "rawSql": "SELECT id, pr_pickup_time AS \"PR pickup time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "B", "select": [ [ @@ -2295,7 +2295,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_review_time AS \"PR review time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", + "rawSql": "SELECT id, pr_review_time AS \"PR review time from project_pr_metrics\" /* first_review_id, */ FROM project_pr_metrics WHERE id = '$pr_id' AND ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "B", "select": [ [ @@ -2373,7 +2373,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR deploy time from cicd_deployment_commits\" FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", + "rawSql": "SELECT ppm.id AS pr_id, ppm.deployment_commit_id, CEIL(CAST(EXTRACT(EPOCH FROM (cdc.finished_date - pr.merged_date)) AS NUMERIC) / NULLIF(60, 0)) AS \"PR deploy time from cicd_deployment_commits\" FROM project_pr_metrics AS ppm LEFT JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.\"environment\" = 'PRODUCTION' AND ppm.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2403,7 +2403,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT id, pr_deploy_time AS \"PR deploy time from project_pr_metrics\" FROM project_pr_metrics WHERE id = '$pr_id' AND project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)", + "rawSql": "SELECT id, pr_deploy_time AS \"PR deploy time from project_pr_metrics\" FROM project_pr_metrics WHERE id = '$pr_id' AND ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "B", "select": [ [ @@ -2481,7 +2481,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS \"PR cycle time from lower-level metrics\", ppm.\"pr_cycle_time\" AS \"PR cycle time from project_pr_metrics\" FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND pr.id = '$pr_id'", + "rawSql": "SELECT ppm.id, (pr_coding_time + CEIL(CAST(EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date)) AS NUMERIC) / NULLIF(60, 0)) + pr_deploy_time) AS \"PR cycle time from lower-level metrics\", ppm.\"pr_cycle_time\" AS \"PR cycle time from project_pr_metrics\" FROM project_pr_metrics AS ppm LEFT JOIN pull_requests AS pr ON ppm.id = pr.id WHERE ('${project:csv}' = '' OR project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND pr.id = '$pr_id'", "refId": "A", "select": [ [ @@ -2617,7 +2617,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YY/MM') AS month, pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _find_median_clt_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT month, id, pr_cycle_time AS change_lead_time_in_minutes, CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time_in_hours, ranks FROM _find_median_clt_each_month_ranks", "refId": "A", "select": [ [ @@ -2809,7 +2809,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, i.type, COUNT(1) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id AND pm.\"table\" = 'boards' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND /* \tand i.type = 'INCIDENT' */ $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table, i.type", + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, i.type, COUNT(1) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id AND pm.\"table\" = 'boards' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND /* \tand i.type = 'INCIDENT' */ $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table, i.type", "refId": "A", "select": [ [ @@ -2930,7 +2930,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i), _metric_recovery_time_2023_report AS (SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN median_recovery_time < 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(elite)' WHEN median_recovery_time < 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(high)' WHEN median_recovery_time < 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(medium)' WHEN median_recovery_time >= 7 * 24 * 60 THEN ROUND(CAST(CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) || '(low)' ELSE 'No data' END END AS median_recovery_time FROM _median_recovery_time, _is_collected_data), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.created_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5), _metric_mttr_2021_report AS (SELECT CASE WHEN ('$dora_report') = '2021' THEN CASE WHEN median_time_to_resolve < 60 THEN 'Less than one hour(elite)' WHEN median_time_to_resolve < 24 * 60 THEN 'Less than one day(high)' WHEN median_time_to_resolve < 7 * 24 * 60 THEN 'Between one day and one week(medium)' WHEN median_time_to_resolve >= 7 * 24 * 60 THEN 'More than one week(low)' ELSE 'N/A. Please check if you have collected incidents.' END END AS median_time_to_resolve FROM _median_mttr) SELECT median_recovery_time AS median_time_in_hour FROM _metric_recovery_time_2023_report WHERE ('$dora_report') = '2023' UNION SELECT median_time_to_resolve AS median_time_to_resolve FROM _metric_mttr_2021_report WHERE ('$dora_report') = '2021'", "refId": "A", "select": [ [ @@ -3045,7 +3045,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, COUNT(1) AS issue_count FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table", + "rawSql": "/* get the incident created within the selected time period in the top-right corner */ SELECT pm.project_name, CASE WHEN ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) THEN 'This project is selected' ELSE 'Not Selected' END AS select_status, i._raw_data_table, COUNT(1) AS issue_count FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.created_date) GROUP BY pm.project_name, \"select_status\", i._raw_data_table", "refId": "A", "select": [ [ @@ -3210,7 +3210,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.created_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY deployment_finished_month ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT deployment_finished_month, MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5 GROUP BY deployment_finished_month), _metric_recovery_time_2023_report AS (SELECT cm.month, CASE WHEN m.median_recovery_time IS NULL THEN 0 ELSE CAST(m.median_recovery_time AS NUMERIC) / NULLIF(60, 0) END AS median_recovery_time_in_hour FROM calendar_months AS cm LEFT JOIN _median_recovery_time AS m ON cm.month = m.deployment_finished_month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: median time to restore service - MTTR */ AS (/* get the number of incidents created each month */ SELECT DISTINCT i.id, TO_CHAR(CAST(i.created_date AS TIMESTAMP), 'YY/MM') AS month, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT i.lead_time_minutes IS NULL), _find_median_mttr_each_month_ranks AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY month ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _mttr AS (SELECT month, MAX(lead_time_minutes) AS median_time_to_resolve FROM _find_median_mttr_each_month_ranks WHERE ranks <= 0.5 GROUP BY month), _metric_mttr_2021_report AS (SELECT cm.month, CASE WHEN m.median_time_to_resolve IS NULL THEN 0 ELSE CAST(m.median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) END AS median_time_to_resolve_in_hour FROM calendar_months AS cm LEFT JOIN _mttr AS m ON cm.month = m.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)) SELECT cm.month, CASE WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour END AS \"${title_value} In Hours\" FROM calendar_months AS cm LEFT JOIN _metric_recovery_time_2023_report AS mrt ON cm.month = mrt.month LEFT JOIN _metric_mttr_2021_report AS mm ON cm.month = mm.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ @@ -3348,7 +3348,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN i.id = NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN i.id = NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate AS (SELECT CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments), _is_collected_data AS (SELECT CASE WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All' WHEN COUNT(i.id) = 0 THEN 'No Incidents' WHEN COUNT(cdc.id) = 0 THEN 'No Deployments' END AS is_collected FROM (SELECT 1) AS dummy LEFT JOIN incidents AS i ON 1 = 1 LEFT JOIN cicd_deployment_commits AS cdc ON 1 = 1) SELECT CASE WHEN ('$dora_report') = '2023' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.05 THEN '0-5%(elite)' WHEN change_failure_rate <= 0.10 THEN '5%-10%(high)' WHEN change_failure_rate <= 0.15 THEN '10%-15%(medium)' WHEN change_failure_rate > 0.15 THEN '> 15%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END WHEN ('$dora_report') = '2021' THEN CASE WHEN is_collected = 'No All' THEN 'N/A. Please check if you have collected deployments/incidents.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN change_failure_rate <= 0.15 THEN '0-15%(elite)' WHEN change_failure_rate <= 0.20 THEN '16%-20%(high)' WHEN change_failure_rate <= 0.30 THEN '21%-30%(medium)' WHEN change_failure_rate > 0.30 THEN '> 30%(low)' ELSE 'N/A. Please check if you have collected deployments/incidents.' END ELSE 'Invalid dora report' END AS change_failure_rate FROM _change_failure_rate, \"_is_collected_data\"", "refId": "A", "select": [ [ @@ -3464,7 +3464,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v)) SELECT deployment_id AS id, 'DEPLOYMENT' AS type, finished_date AS time FROM _deployments UNION SELECT issue_id AS id, 'INCIDENT' AS type, created_date AS time FROM _incidents ORDER BY time NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project)) SELECT deployment_id AS id, 'DEPLOYMENT' AS type, finished_date AS time FROM _deployments UNION SELECT issue_id AS id, 'INCIDENT' AS type, created_date AS time FROM _incidents ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3564,7 +3564,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT cdc.cicd_deployment_id AS deployment_id /* in CFR we use deployment_commit_id as the deployment_id in a specific repo */, cdc.finished_date, pim.id AS incident_id, CASE WHEN NOT pim.id IS NULL THEN 'TRUE' ELSE 'FALSE' END AS has_failure FROM cicd_deployment_commits AS cdc LEFT JOIN project_incident_deployment_relationships AS pim ON cdc.cicd_deployment_id = pim.deployment_id LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(cdc.finished_date) ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT cdc.cicd_deployment_id AS deployment_id /* in CFR we use deployment_commit_id as the deployment_id in a specific repo */, cdc.finished_date, pim.id AS incident_id, CASE WHEN NOT pim.id IS NULL THEN 'TRUE' ELSE 'FALSE' END AS has_failure FROM cicd_deployment_commits AS cdc LEFT JOIN project_incident_deployment_relationships AS pim ON cdc.cicd_deployment_id = pim.deployment_id LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(cdc.finished_date) ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -3711,7 +3711,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", + "rawSql": "/* Metric 3: change failure rate per month */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2), _change_failure_rate_for_each_month AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YY/MM') AS month, CASE WHEN COUNT(deployment_id) IS NULL THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS change_failure_rate FROM _failure_caused_by_deployments GROUP BY 1) SELECT cm.month, cfr.change_failure_rate AS \"Change Failure Rate\" FROM calendar_months AS cm LEFT JOIN _change_failure_rate_for_each_month AS cfr ON cm.month = cfr.month WHERE month_timestamp BETWEEN CAST(TO_CHAR(CAST($__timeFrom() AS TIMESTAMP), 'YYYY-MM-01') AS DATE) AND CAST(TO_CHAR(CAST($__timeTo() AS TIMESTAMP), 'YYYY-MM-01') AS DATE)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/dora-details-change-failure-rate.json b/grafana/dashboards/postgresql/dora-details-change-failure-rate.json index 699fbaa77c8..1295aa98cf1 100644 --- a/grafana/dashboards/postgresql/dora-details-change-failure-rate.json +++ b/grafana/dashboards/postgresql/dora-details-change-failure-rate.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) AS \"change_failure_rate\" FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) AS \"change_failure_rate\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -365,7 +365,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id AS \"deployment_id\", d.deployment_finished_date, pim.id AS incident_id, i.title, i.url, i.url AS \"metric_hidden\", i.created_date /* i.resolution_date, */ FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN issues AS i ON pim.id = i.id WHERE NOT pim.id IS NULL ORDER BY 2 NULLS FIRST) SELECT * FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id AS \"deployment_id\", d.deployment_finished_date, pim.id AS incident_id, i.title, i.url, i.url AS \"metric_hidden\", i.created_date /* i.resolution_date, */ FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN issues AS i ON pim.id = i.id WHERE NOT pim.id IS NULL ORDER BY 2 NULLS FIRST) SELECT * FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT SUM(has_incident) AS \"incident count\", COUNT(deployment_id) AS \"deployment count\" FROM _failure_caused_by_deployments", + "rawSql": "/* Metric 3: change failure rate */ WITH _deployments AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused_by_deployments AS (/* calculate the number of incidents caused by each deployment */ SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT i.id) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) SELECT SUM(has_incident) AS \"incident count\", COUNT(deployment_id) AS \"deployment count\" FROM _failure_caused_by_deployments", "refId": "A", "select": [ [ @@ -627,7 +627,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT finished_date AS \"Time (Ascending)\", deployment_id AS \"Entity ID\", 'DEPLOYMENT' AS \"Entity Type (Deployment/Incident)\" FROM _deployments UNION SELECT created_date AS \"Time (Ascending)\", issue_id AS \"Entity ID\", 'INCIDENT' AS \"Entity Type (Deployment/Incident)\" FROM _incidents ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _deployments AS (SELECT DISTINCT d.cicd_deployment_id AS deployment_id, d.result, d.environment, d.finished_date, d.cicd_scope_id, pm.project_name FROM cicd_deployment_commits AS d JOIN project_mapping AS pm ON d.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE d.result /* only result needs to specified, not environment */ = 'SUCCESS' /* choose your project_name */ AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(d.finished_date)), _incidents AS (SELECT DISTINCT i.id AS issue_id, i.created_date, pm.project_name FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" WHERE pm.project_name /* choose your project_name */ IN ($project) AND $__timeFilter(i.created_date)) SELECT finished_date AS \"Time (Ascending)\", deployment_id AS \"Entity ID\", 'DEPLOYMENT' AS \"Entity Type (Deployment/Incident)\" FROM _deployments UNION SELECT created_date AS \"Time (Ascending)\", issue_id AS \"Entity ID\", 'INCIDENT' AS \"Entity Type (Deployment/Incident)\" FROM _incidents ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/dora-details-deployment-frequency.json b/grafana/dashboards/postgresql/dora-details-deployment-frequency.json index 93ec2ba7998..36d078547e8 100644 --- a/grafana/dashboards/postgresql/dora-details-deployment-frequency.json +++ b/grafana/dashboards/postgresql/dora-details-deployment-frequency.json @@ -257,7 +257,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.display_title, cdc.url, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, CASE WHEN display_title = '' THEN 'N/A' ELSE display_title END AS display_title, url, url AS metric_hidden, result /* a deployment may have multiple deployment_commits */ /* id as deployment_commit_id, */, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date) ORDER BY finished_date DESC NULLS LAST", + "rawSql": "WITH _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.display_title, cdc.url, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION') SELECT project_name, cicd_deployment_id AS deployment_id, CASE WHEN display_title = '' THEN 'N/A' ELSE display_title END AS display_title, url, url AS metric_hidden, result /* a deployment may have multiple deployment_commits */ /* id as deployment_commit_id, */, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date) ORDER BY finished_date DESC NULLS LAST", "refId": "A", "select": [ [ @@ -375,7 +375,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_days AS (/* Construct the last few calendar days within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT TO_CHAR(CAST(last_few_calendar_days.day AS TIMESTAMP), 'YYYY-MM-DD') AS day, COALESCE(COUNT(d.finished_date), 0) AS successful_deployments_to_prod FROM last_few_calendar_days LEFT JOIN _deployments AS d ON last_few_calendar_days.day = CAST(d.finished_date AS DATE) GROUP BY 1 ORDER BY 1 DESC NULLS LAST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_days AS (/* Construct the last few calendar days within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _deployment_commit_rank AS (SELECT pm.project_name, CASE WHEN cdc._raw_data_table <> '' THEN cdc._raw_data_table ELSE cdc.cicd_scope_id END AS _raw_data_table, cdc.id, cdc.cicd_deployment_id, cdc.cicd_scope_id, result, environment, finished_date, ROW_NUMBER() OVER (PARTITION BY cdc.cicd_deployment_id ORDER BY finished_date DESC NULLS LAST) AS _deployment_commit_rank FROM cicd_deployment_commits AS cdc LEFT JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND result = 'SUCCESS' AND environment = 'PRODUCTION'), _deployments AS (SELECT project_name, cicd_deployment_id AS deployment_id, id AS deployment_commit_id /* a deployment may have multiple deployment_commits */, result, environment, finished_date FROM _deployment_commit_rank WHERE _deployment_commit_rank = 1 AND $__timeFilter(finished_date)) SELECT TO_CHAR(CAST(last_few_calendar_days.day AS TIMESTAMP), 'YYYY-MM-DD') AS day, COALESCE(COUNT(d.finished_date), 0) AS successful_deployments_to_prod FROM last_few_calendar_days LEFT JOIN _deployments AS d ON last_few_calendar_days.day = CAST(d.finished_date AS DATE) GROUP BY 1 ORDER BY 1 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -503,7 +503,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), calendar_weeks AS (SELECT DISTINCT CAST(CAST(day AS DATE) + -(EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM last_few_calendar_months ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') AS week, days_deployed FROM calendar_weeks AS cw LEFT JOIN _days_weekly_deploy AS b ON cw.start_of_week = b.week", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), calendar_weeks AS (SELECT DISTINCT CAST(CAST(day AS DATE) + -(EXTRACT(ISODOW FROM CAST(day AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM last_few_calendar_months ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') AS week, days_deployed FROM calendar_weeks AS cw LEFT JOIN _days_weekly_deploy AS b ON cw.start_of_week = b.week", "refId": "A", "select": [ [ @@ -633,7 +633,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month, days_deployed FROM _days_monthly_deploy", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month, days_deployed FROM _days_monthly_deploy", "refId": "A", "select": [ [ @@ -762,7 +762,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month - INTERVAL '5 MONTH' AS TIMESTAMP), 'YY/MM') || ' ~ ' || TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month_range, days_deployed_per_six_months AS days_deployed FROM _days_six_months_deploy ORDER BY month NULLS FIRST", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy) SELECT TO_CHAR(CAST(month - INTERVAL '5 MONTH' AS TIMESTAMP), 'YY/MM') || ' ~ ' || TO_CHAR(CAST(month AS TIMESTAMP), 'YY/MM') AS month_range, days_deployed_per_six_months AS days_deployed FROM _days_six_months_deploy ORDER BY month NULLS FIRST", "refId": "A", "select": [ [ @@ -925,7 +925,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_week AS \"Median weekly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\", */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_week AS \"Median weekly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\", */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1063,7 +1063,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_month AS \"Median monthly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_month AS \"Median monthly deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_six_months as \"Median semi-annual deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ @@ -1201,7 +1201,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_six_months AS \"Median semi-annual deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", + "rawSql": "/* Metric 1: Deployment Frequency */ WITH last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS day FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _production_deployment_days AS (/* When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date. */ SELECT cdc.cicd_deployment_id AS deployment_id, MAX(CAST(cdc.finished_date AS DATE)) AS day FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1), _days_weekly_deploy AS (/* calculate the number of deployment days every week */ SELECT CAST(last_few_calendar_months.day + -(EXTRACT(ISODOW FROM last_few_calendar_months.day) - 1) * INTERVAL '1 day' AS DATE) AS week, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS weeks_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY week), _days_monthly_deploy AS (/* calculate the number of deployment days every month */ SELECT CAST(last_few_calendar_months.day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(last_few_calendar_months.day AS DATE)) + 1) AS DATE) AS month, MAX(CASE WHEN NOT _production_deployment_days.day IS NULL THEN 1 ELSE NULL END) AS months_deployed, COUNT(DISTINCT _production_deployment_days.day) AS days_deployed FROM last_few_calendar_months LEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day GROUP BY month), _days_six_months_deploy AS (SELECT month, SUM(days_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS days_deployed_per_six_months, COUNT(months_deployed) OVER (ORDER BY month NULLS FIRST ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS months_deployed_count, ROW_NUMBER() OVER (PARTITION BY ((EXTRACT(YEAR FROM CAST(month AS TIMESTAMP)) * 12 + EXTRACT(MONTH FROM CAST(month AS TIMESTAMP))) / 6) ORDER BY month DESC NULLS LAST) AS rn FROM _days_monthly_deploy), _median_number_of_deployment_days_per_week_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_weekly_deploy), _median_number_of_deployment_days_per_week AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_week FROM _median_number_of_deployment_days_per_week_ranks WHERE ranks <= 0.5), _median_number_of_deployment_days_per_month_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed NULLS FIRST) AS ranks FROM _days_monthly_deploy), _median_number_of_deployment_days_per_month AS (SELECT MAX(days_deployed) AS median_number_of_deployment_days_per_month FROM _median_number_of_deployment_days_per_month_ranks WHERE ranks <= 0.5), _days_per_six_months_deploy_by_filter AS (SELECT month, days_deployed_per_six_months, months_deployed_count FROM _days_six_months_deploy WHERE rn % 6 = 1), _median_number_of_deployment_days_per_six_months_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY days_deployed_per_six_months NULLS FIRST) AS ranks FROM _days_per_six_months_deploy_by_filter), _median_number_of_deployment_days_per_six_months AS (SELECT MIN(days_deployed_per_six_months) AS median_number_of_deployment_days_per_six_months, MIN(months_deployed_count) AS is_collected FROM _median_number_of_deployment_days_per_six_months_ranks WHERE ranks >= 0.5), _tolal_production_deployment_days AS (SELECT COUNT(*) AS tpdd, COUNT(DISTINCT day) AS dtpdd FROM _production_deployment_days) SELECT median_number_of_deployment_days_per_six_months AS \"Median semi-annual deployment days\" /* tpdd as \"Production Deploy Counts\", */ /* dtpdd as \"Production Deployment Days\", */ /* median_number_of_deployment_days_per_week as \"Median weekly deployment days\" */ /* median_number_of_deployment_days_per_month as \"Median monthly deployment days\" */ FROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months, _tolal_production_deployment_days", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json b/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json index 441c5d5dabb..9785a9f8590 100644 --- a/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json +++ b/grafana/dashboards/postgresql/dora-details-failed-deployment-recovery-time.json @@ -121,7 +121,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN NOT median_recovery_time IS NULL THEN (CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0))::TEXT ELSE 'No data' END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _recovery_time_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY (EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60) NULLS FIRST) AS ranks FROM _incidents_for_deployments), _median_recovery_time AS (SELECT MAX((EXTRACT(EPOCH FROM (incident_resolution_date - deployment_finished_date))/60)) AS median_recovery_time FROM _recovery_time_ranks WHERE ranks <= 0.5), _is_collected_data AS (SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM _deployments) AND NOT EXISTS(SELECT 1 FROM incidents) THEN 'No deployments and incidents' WHEN NOT EXISTS(SELECT 1 FROM _deployments) THEN 'No Deployments' WHEN NOT EXISTS(SELECT 1 FROM incidents) THEN 'No Incidents' ELSE 'No incidents are mapped to deployments' END AS is_collected FROM _deployments AS d, _incidents_for_deployments AS i) SELECT CASE WHEN is_collected = 'No deployments and incidents' THEN 'N/A. Please check if you have collected deployments and incidents.' WHEN is_collected = 'No Deployments' THEN 'N/A. Please check if you have collected deployments.' WHEN is_collected = 'No Incidents' THEN 'N/A. Please check if you have collected incidents.' WHEN NOT median_recovery_time IS NULL THEN (CAST(median_recovery_time AS NUMERIC) / NULLIF(60, 0))::TEXT ELSE 'No data' END AS median_recovery_time_in_hours FROM _median_recovery_time, _is_collected_data", "refId": "D", "sql": { "columns": [ @@ -314,7 +314,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT fd.deployment_id AS \"deployment_id\", fd.deployment_finished_date, i.id AS incident_caused_by_deployment /* date_format(fd.deployment_finished_date, '%y/%m') as deployment_finished_month, */, i.title AS incident_title, i.url AS incident_url, i.url AS \"metric_hidden\", i.resolution_date AS incident_resolution_date /* i.created_date as incident_create_date, */, (EXTRACT(EPOCH FROM (i.resolution_date - fd.deployment_finished_date))/3600) AS \"failed_deployment_recovery_time\" FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)) SELECT * FROM _incidents_for_deployments WHERE NOT incident_resolution_date IS NULL", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT fd.deployment_id AS \"deployment_id\", fd.deployment_finished_date, i.id AS incident_caused_by_deployment /* date_format(fd.deployment_finished_date, '%y/%m') as deployment_finished_month, */, i.title AS incident_title, i.url AS incident_url, i.url AS \"metric_hidden\", i.resolution_date AS incident_resolution_date /* i.created_date as incident_create_date, */, (EXTRACT(EPOCH FROM (i.resolution_date - fd.deployment_finished_date))/3600) AS \"failed_deployment_recovery_time\" FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)) SELECT * FROM _incidents_for_deployments WHERE NOT incident_resolution_date IS NULL", "refId": "A", "select": [ [ @@ -409,7 +409,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", + "rawSql": "/* ***** 2023 report ***** -- */ /* Metric 4: Failed deployment recovery time */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents_for_deployments AS (SELECT i.id AS incident_id, i.created_date AS incident_create_date, i.resolution_date AS incident_resolution_date, fd.deployment_id AS caused_by_deployment, fd.deployment_finished_date, TO_CHAR(CAST(fd.deployment_finished_date AS TIMESTAMP), 'YY/MM') AS deployment_finished_month FROM incidents AS i LEFT JOIN project_incident_deployment_relationships AS pim ON i.id = pim.id JOIN _deployments AS fd ON pim.deployment_id = fd.deployment_id WHERE $__timeFilter(i.resolution_date)), _incidents /* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND i.\"table\" = pm.\"table\" AND pm.\"table\" = 'boards' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.created_date)) SELECT COUNT(incident_id) AS total_count FROM _incidents_for_deployments", "refId": "D", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json b/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json index 546728bd1ac..f944cc21747 100644 --- a/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json +++ b/grafana/dashboards/postgresql/dora-details-lead-timefor-changes.json @@ -132,7 +132,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -240,7 +240,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS \"Coding Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT AVG(coding_time) AS \"Coding Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -379,7 +379,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -518,7 +518,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS \"Review Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT AVG(review_time) AS \"Review Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -657,7 +657,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.created_date AS pr_issued_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(prm.pr_deployed_date) AND /* and pr.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs", "refId": "A", "select": [ [ @@ -833,7 +833,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, pr.title, pr.url, pr.created_date, ppm.pr_coding_time, ppm.pr_pickup_time, ppm.pr_review_time, ppm.pr_deploy_time, ppm.first_commit_sha, prc.commit_authored_date, cdc.cicd_deployment_id, cdc.name, cdc.finished_date, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id JOIN pull_request_commits AS prc ON prc.commit_sha = ppm.first_commit_sha WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)) SELECT title AS \"PR title\" /* id as \"PR id\", */, url AS \"PR url\", url AS metric_hidden, first_commit_sha AS \"First commit sha\" /* created_date as \"PR created_date\", */, commit_authored_date AS \"First commit authored date\", cicd_deployment_id AS \"Deployment id\", finished_date AS \"Deployment finished_date\" /* name as \"Deployment name\", */, CAST(pr_coding_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_coding_time\", CAST(pr_pickup_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_pickup_time\", CAST(pr_review_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_review_time\", CAST(pr_deploy_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_deploy_time\", CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time FROM _pr_stats", + "rawSql": "WITH _pr_stats AS (/* get the cycle time of PRs deployed by the deployments finished each month */ SELECT DISTINCT pr.id, pr.title, pr.url, pr.created_date, ppm.pr_coding_time, ppm.pr_pickup_time, ppm.pr_review_time, ppm.pr_deploy_time, ppm.first_commit_sha, prc.commit_authored_date, cdc.cicd_deployment_id, cdc.name, cdc.finished_date, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id JOIN pull_request_commits AS prc ON prc.commit_sha = ppm.first_commit_sha WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)) SELECT title AS \"PR title\" /* id as \"PR id\", */, url AS \"PR url\", url AS metric_hidden, first_commit_sha AS \"First commit sha\" /* created_date as \"PR created_date\", */, commit_authored_date AS \"First commit authored date\", cicd_deployment_id AS \"Deployment id\", finished_date AS \"Deployment finished_date\" /* name as \"Deployment name\", */, CAST(pr_coding_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_coding_time\", CAST(pr_pickup_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_pickup_time\", CAST(pr_review_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_review_time\", CAST(pr_deploy_time AS NUMERIC) / NULLIF(60, 0) AS \"pr_deploy_time\", CAST(pr_cycle_time AS NUMERIC) / NULLIF(60, 0) AS change_lead_time FROM _pr_stats", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json b/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json index 153e830203b..f9e4cdabda0 100644 --- a/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json +++ b/grafana/dashboards/postgresql/dora-details-timeto-restore-service.json @@ -121,7 +121,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5) SELECT CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS median_time_to_resolve_in_hours FROM _median_mttr", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)), _median_mttr_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY lead_time_minutes NULLS FIRST) AS ranks FROM _incidents), _median_mttr AS (SELECT MAX(lead_time_minutes) AS median_time_to_resolve FROM _median_mttr_ranks WHERE ranks <= 0.5) SELECT CAST(median_time_to_resolve AS NUMERIC) / NULLIF(60, 0) AS median_time_to_resolve_in_hours FROM _median_mttr", "refId": "D", "sql": { "columns": [ @@ -301,7 +301,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id AS \"incident_id\", i.title, i.url, i.url AS \"metric_hidden\", i.resolution_date /* i.created_date, */, CAST(CAST(lead_time_minutes AS NUMERIC) / NULLIF(60, 0) AS BIGINT) AS time_to_restore_service FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND $__timeFilter(i.resolution_date)) SELECT * FROM _incidents ORDER BY resolution_date DESC NULLS LAST", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id AS \"incident_id\", i.title, i.url, i.url AS \"metric_hidden\", i.resolution_date /* i.created_date, */, CAST(CAST(lead_time_minutes AS NUMERIC) / NULLIF(60, 0) AS BIGINT) AS time_to_restore_service FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.resolution_date)) SELECT * FROM _incidents ORDER BY resolution_date DESC NULLS LAST", "refId": "A", "select": [ [ @@ -396,7 +396,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '$project' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$project]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date)) SELECT COUNT(id) AS \"incident count\" FROM _incidents", + "rawSql": "/* ***** 2021 report ***** -- */ /* Metric 4: Median time to restore service */ WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _incidents AS (/* get the incidents created within the selected time period in the top-right corner */ SELECT DISTINCT i.id, CAST(lead_time_minutes AS BIGINT) AS lead_time_minutes FROM incidents AS i JOIN project_mapping AS pm ON i.scope_id = pm.row_id AND pm.\"table\" = i.\"table\" WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.created_date)) SELECT COUNT(id) AS \"incident count\" FROM _incidents", "refId": "D", "sql": { "columns": [ diff --git a/grafana/dashboards/postgresql/engineering-overview.json b/grafana/dashboards/postgresql/engineering-overview.json index 28bffc3fcb9..41581f05d6a 100644 --- a/grafana/dashboards/postgresql/engineering-overview.json +++ b/grafana/dashboards/postgresql/engineering-overview.json @@ -119,7 +119,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) AND i.type = 'BUG' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND ('${priority:csv}' = '' OR i.priority::text = ANY(ARRAY[${priority:singlequote}]::text[])) AND i.type = 'BUG' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -253,7 +253,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT i.id) AS defect_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) AND i.type = 'BUG' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, defect_count FROM _issues ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, COUNT(DISTINCT i.id) AS defect_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND ('${priority:csv}' = '' OR i.priority::text = ANY(ARRAY[${priority:singlequote}]::text[])) AND i.type = 'BUG' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, defect_count FROM _issues ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -360,7 +360,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type::text IN (SELECT v FROM unnest(CASE WHEN '${type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${type}]::text[] END) v) AND i.status = 'DONE' AND i.resolution_date BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND ('${type:csv}' = '' OR i.type::text = ANY(ARRAY[${type:singlequote}]::text[])) AND i.status = 'DONE' AND i.resolution_date BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -502,7 +502,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, issue_lead_time AS \"Mean Requirement Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -591,7 +591,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT author_name) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT author_name) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -725,7 +725,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _developers AS (SELECT CAST(c.authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(c.authored_date AS DATE)) + 1) AS time, COUNT(DISTINCT author_name) AS developer_count FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(c.authored_date) AND c.authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, developer_count FROM _developers ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _developers AS (SELECT CAST(c.authored_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(c.authored_date AS DATE)) + 1) AS time, COUNT(DISTINCT author_name) AS developer_count FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(c.authored_date) AND c.authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, developer_count FROM _developers ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -840,7 +840,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'), _total_num_issues AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH') SELECT NOW() AS time, 100 - CAST(100 * (SELECT 1.0 * num_issues_with_sprint_updated FROM _num_issues_with_sprint_updated) AS NUMERIC) / NULLIF((SELECT total_num_issues FROM _total_num_issues), 0) AS ratio", + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'), _total_num_issues AS (SELECT NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH') SELECT NOW() AS time, 100 - CAST(100 * (SELECT 1.0 * num_issues_with_sprint_updated FROM _num_issues_with_sprint_updated) AS NUMERIC) / NULLIF((SELECT total_num_issues FROM _total_num_issues), 0) AS ratio", "refId": "A", "select": [ [ @@ -981,7 +981,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time), _total_num_issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT x.time, 100 - 100 * (CAST(1.0 * x.num_issues_with_sprint_updated AS NUMERIC) / NULLIF(y.total_num_issues, 0)) AS delivery_rate FROM _num_issues_with_sprint_updated AS x JOIN _total_num_issues AS y ON x.time = y.time", + "rawSql": "WITH _num_issues_with_sprint_updated AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS num_issues_with_sprint_updated FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN issue_changelogs AS c ON i.id = c.issue_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND c.field_name = 'Sprint' AND c.original_from_value <> '' AND c.original_to_value <> '' AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time), _total_num_issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, NULLIF(COUNT(DISTINCT i.id), 0) AS total_num_issues FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND i.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT x.time, 100 - 100 * (CAST(1.0 * x.num_issues_with_sprint_updated AS NUMERIC) / NULLIF(y.total_num_issues, 0)) AS delivery_rate FROM _num_issues_with_sprint_updated AS x JOIN _total_num_issues AS y ON x.time = y.time", "refId": "A", "select": [ [ @@ -1090,7 +1090,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE NOT pr.merged_date IS NULL AND CAST(pr.merged_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE NOT pr.merged_date IS NULL AND CAST(pr.merged_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1231,7 +1231,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT CAST(pr.merged_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.merged_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_merged_count FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_merged_count FROM _merged_prs ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _merged_prs AS (SELECT CAST(pr.merged_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.merged_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_merged_count FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY time) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_merged_count FROM _merged_prs ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1344,7 +1344,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1483,7 +1483,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\"", + "rawSql": "SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, CAST(100 * COUNT(DISTINCT CASE WHEN pr.id IN (SELECT pull_request_id FROM pull_request_issues) THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS unlinked_pr_rate FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND $__timeFilter(created_date) AND created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\"", "refId": "A", "select": [ [ @@ -1585,7 +1585,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND (EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4 AND CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' GROUP BY \"author_name\", CAST(authored_date AS DATE)) SELECT CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) FROM _commits_groupby_name_and_date", + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND (EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4 AND CAST(authored_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH' GROUP BY \"author_name\", CAST(authored_date AS DATE)) SELECT CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) FROM _commits_groupby_name_and_date", "refId": "A", "select": [ [ @@ -1717,7 +1717,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND ((EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4) AND $__timeFilter(authored_date) AND authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1, 2) SELECT _day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(_day AS DATE)) + 1) AS time, CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) AS working_days_percentatages_per_month FROM _commits_groupby_name_and_date GROUP BY time", + "rawSql": "WITH _commits_groupby_name_and_date AS (SELECT author_name, CAST(authored_date AS DATE) AS _day, COUNT(DISTINCT c.sha) FROM commits AS c JOIN repo_commits AS rc ON c.sha = rc.commit_sha JOIN project_mapping AS pm ON rc.repo_id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND ((EXTRACT(ISODOW FROM authored_date) - 1) BETWEEN 0 AND 4) AND $__timeFilter(authored_date) AND authored_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1, 2) SELECT _day + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(_day AS DATE)) + 1) AS time, CAST(100 * COUNT(*) AS NUMERIC) / NULLIF((COUNT(DISTINCT author_name) * COUNT(DISTINCT _day)), 0) AS working_days_percentatages_per_month FROM _commits_groupby_name_and_date GROUP BY time", "refId": "A", "select": [ [ @@ -1828,7 +1828,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND CAST(pr.created_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -1969,7 +1969,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS pr_time_to_merge_in_days FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.created_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\" ORDER BY time NULLS FIRST", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS pr_time_to_merge_in_days FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND $__timeFilter(pr.created_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY \"time\" ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2075,7 +2075,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS \"Bug\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS \"Strategic\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS \"Non-Strategic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT CASE WHEN i.type = 'BUG' THEN i.id ELSE NULL END) AS \"Bug\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key <> '' THEN i.id ELSE NULL END) AS \"Strategic\", COUNT(DISTINCT CASE WHEN i.type <> 'BUG' AND epic_key = '' THEN i.id ELSE NULL END) AS \"Non-Strategic\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT i.resolution_date IS NULL AND CAST(resolution_date AS DATE) BETWEEN TO_DATE('$month', 'YYYY-MM-DD') AND TO_DATE('$month', 'YYYY-MM-DD') + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2207,7 +2207,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.priority AS \"Priority\", AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS \"Average Age\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.status = 'TODO' AND i.type = 'BUG' AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY i.priority", + "rawSql": "SELECT i.priority AS \"Priority\", AVG(CAST((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS \"Average Age\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND i.status = 'TODO' AND i.type = 'BUG' AND ('${priority:csv}' = '' OR i.priority::text = ANY(ARRAY[${priority:singlequote}]::text[])) GROUP BY i.priority", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json index 1ccbb6f0510..ed2cb78da9f 100644 --- a/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json +++ b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time-team-view.json @@ -210,7 +210,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: Total PR Opened\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: Total PR Opened\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team1: Total PR Opened\", COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team2: Total PR Opened\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -380,7 +380,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Opened per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Opened per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Opened per Member\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: PR Opened per Member\", CAST(COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: PR Opened per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Opened per Member\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -533,7 +533,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS \"Team1: Total PR Merged\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS \"Team2: Total PR Merged\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) AND NOT merged_date IS NULL THEN id WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) AND merged_date IS NULL THEN NULL END) AS \"Team1: Total PR Merged\", COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) AND NOT merged_date IS NULL THEN id WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) AND merged_date IS NULL THEN NULL END) AS \"Team2: Total PR Merged\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -686,7 +686,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT merged_date IS NULL THEN id WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN id END) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged per Member\" FROM _prs GROUP BY 1", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) AND NOT merged_date IS NULL THEN id WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) AND NOT merged_date IS NULL THEN id WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) AND merged_date IS NULL THEN NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: PR Merged per Member\", CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN id END) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged per Member\" FROM _prs GROUP BY 1", "refId": "A", "select": [ [ @@ -866,7 +866,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: Issues Completed\" /* count(i.id) AS \"Issues Opened\", */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: Issues Completed\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'DONE' AND ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team1: Issues Completed\" /* count(i.id) AS \"Issues Opened\", */, COUNT(DISTINCT CASE WHEN status = 'DONE' AND ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team2: Issues Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1037,7 +1037,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: Issues Completed per Member\", CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: Issues Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: Issues Completed per Member\", CAST(COUNT(DISTINCT CASE WHEN status = 'DONE' AND ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: Issues Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Issues Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1234,7 +1234,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN story_point ELSE 0 END) AS \"Team1: Story Points Completed\" /* count(i.id) AS \"Issues Opened\", */, SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN story_point ELSE 0 END) AS \"Team2: Story Points Completed\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN status = 'DONE' AND ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN story_point ELSE 0 END) AS \"Team1: Story Points Completed\" /* count(i.id) AS \"Issues Opened\", */, SUM(CASE WHEN status = 'DONE' AND ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN story_point ELSE 0 END) AS \"Team2: Story Points Completed\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1405,7 +1405,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: Story Points Completed per Member\", CAST(SUM(CASE WHEN status = 'DONE' AND team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: Story Points Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", + "rawSql": "WITH _issues AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN status = 'DONE' AND ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: Story Points Completed per Member\", CAST(SUM(CASE WHEN status = 'DONE' AND ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN story_point ELSE 0 END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: Story Points Completed per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: Story Points Completed per Member\" FROM _issues GROUP BY 1", "refId": "A", "select": [ [ @@ -1602,7 +1602,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, prc.id AS comment_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Review Depth\", CAST(COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Review Depth\", CAST(COUNT(DISTINCT comment_id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Review Depth\" FROM _merged_prs GROUP BY 1", + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, prc.id AS comment_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: PR Review Depth\", CAST(COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN comment_id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: PR Review Depth\", CAST(COUNT(DISTINCT comment_id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Review Depth\" FROM _merged_prs GROUP BY 1", "refId": "A", "select": [ [ @@ -1773,7 +1773,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, pr.merge_commit_sha, c.additions + c.deletions AS loc, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND pr.status = 'MERGED' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: PR Size\", CAST(SUM(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: PR Size\", CAST(SUM(loc) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged Size\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, pr.merge_commit_sha, c.additions + c.deletions AS loc, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND pr.status = 'MERGED' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(SUM(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: PR Size\", CAST(SUM(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN loc ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: PR Size\", CAST(SUM(loc) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: PR Merged Size\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1970,7 +1970,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS \"Team1: P0/P1 Bugs\", COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS \"Team2: P0/P1 Bugs\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team1: P0/P1 Bugs\", COUNT(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS \"Team2: P0/P1 Bugs\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2141,7 +2141,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v)), 0) AS \"Team1: P0/P1 Bugs per Member\", CAST(COUNT(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v)), 0) AS \"Team2: P0/P1 Bugs per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _bugs AS (SELECT DISTINCT i.id, i.url, i.created_date, i.status, i.assignee_id, i.story_point, i.priority, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id JOIN user_accounts AS ua ON i.assignee_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND i.type = 'BUG' ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[]))), 0) AS \"Team1: P0/P1 Bugs per Member\", CAST(COUNT(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN id ELSE NULL END) AS NUMERIC) / NULLIF((SELECT COUNT(DISTINCT user_id) FROM team_users WHERE ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[]))), 0) AS \"Team2: P0/P1 Bugs per Member\", CAST(COUNT(DISTINCT id) AS NUMERIC) / NULLIF((SELECT COUNT(*) FROM users), 0) AS \"Org: P0/P1 Bugs per Member\" FROM _bugs WHERE priority /* please choose the priorities in the filter above */ IN (${priority}) GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2335,7 +2335,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN cycle_time END) AS \"Team1: Avg Cycle Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN cycle_time END) AS \"Team2: Avg Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN cycle_time END) AS \"Team1: Avg Cycle Time(h)\", AVG(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN cycle_time END) AS \"Team2: Avg Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2502,7 +2502,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN coding_time END) AS \"Team1: Avg Coding Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN coding_time END) AS \"Team2: Avg Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN coding_time END) AS \"Team1: Avg Coding Time(h)\", AVG(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN coding_time END) AS \"Team2: Avg Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2669,7 +2669,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN pickup_time END) AS \"Team1: Avg Pickup Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN pickup_time END) AS \"Team2: Avg Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN pickup_time END) AS \"Team1: Avg Pickup Time(h)\", AVG(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN pickup_time END) AS \"Team2: Avg Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2836,7 +2836,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN review_time END) AS \"Team1: Avg Review Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN review_time END) AS \"Team2: Avg Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN review_time END) AS \"Team1: Avg Review Time(h)\", AVG(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN review_time END) AS \"Team2: Avg Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -3003,7 +3003,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) THEN deploy_time END) AS \"Team1: Avg Deploy Time(h)\", AVG(CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) THEN deploy_time END) AS \"Team2: Avg Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id LEFT JOIN user_accounts AS ua ON pr.author_id = ua.account_id LEFT JOIN users AS u ON ua.user_id = u.id LEFT JOIN team_users AS tu ON u.id = tu.user_id LEFT JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.merged_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3, 4, 5, 6, 7, 8) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) THEN deploy_time END) AS \"Team1: Avg Deploy Time(h)\", AVG(CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) THEN deploy_time END) AS \"Team2: Avg Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -3196,7 +3196,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team1}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team1}]::text[] END) v) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team1: PRs Merged w/o Review\", COUNT(DISTINCT CASE WHEN team_id::text IN (SELECT v FROM unnest(CASE WHEN '${team2}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${team2}]::text[] END) v) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team2: PRs Merged w/o Review\" FROM _merged_prs GROUP BY 1", + "rawSql": "WITH _merged_prs AS (SELECT DISTINCT pr.id, pr.url, pr.created_date, pr.merged_date, pr.author_id, u.id AS user_id, u.name AS user_name, t.id AS team_id, t.name AS team FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' JOIN user_accounts AS ua ON pr.author_id = ua.account_id JOIN users AS u ON ua.user_id = u.id JOIN team_users AS tu ON u.id = tu.user_id JOIN teams AS t ON tu.team_id = t.id WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL ORDER BY 1 NULLS FIRST) SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN ('${team1:csv}' = '' OR team_id::text = ANY(ARRAY[${team1:singlequote}]::text[])) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team1: PRs Merged w/o Review\", COUNT(DISTINCT CASE WHEN ('${team2:csv}' = '' OR team_id::text = ANY(ARRAY[${team2:singlequote}]::text[])) AND NOT id IN (SELECT pull_request_id FROM pull_request_comments) THEN id ELSE NULL END) AS \"Team2: PRs Merged w/o Review\" FROM _merged_prs GROUP BY 1", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json index 4e0ddb8b6e1..6aa997c057b 100644 --- a/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json +++ b/grafana/dashboards/postgresql/engineering-throughput-and-cycle-time.json @@ -208,7 +208,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT pr.id) AS \"PR: Opened\", COUNT(DISTINCT CASE WHEN NOT pr.merged_date IS NULL THEN id ELSE NULL END) AS \"PR: Merged\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT pr.id) AS \"PR: Opened\", COUNT(DISTINCT CASE WHEN NOT pr.merged_date IS NULL THEN id ELSE NULL END) AS \"PR: Merged\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -360,7 +360,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"Issues Opened\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Issues Completed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"Issues Opened\", COUNT(DISTINCT CASE WHEN i.status = 'DONE' THEN i.id ELSE NULL END) AS \"Issues Completed\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -483,7 +483,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS \"Story Points Completed\" FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v)) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN i.status = 'DONE' THEN i.story_point ELSE 0 END) AS \"Story Points Completed\" FROM (SELECT DISTINCT i.id, i.resolution_date, i.status, i.story_point FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.resolution_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[]))) AS i GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -622,7 +622,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT prc.id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS \"PR Review Depth\" FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL GROUP BY 1", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT prc.id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS \"PR Review Depth\" FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL GROUP BY 1", "refId": "A", "select": [ [ @@ -745,7 +745,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"P0/P1 Bugs\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND i.type = 'BUG' AND i.priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) GROUP BY 1", + "rawSql": "SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS \"P0/P1 Bugs\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id JOIN project_mapping AS pm ON b.id = pm.row_id WHERE $__timeFilter(i.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND i.type = 'BUG' AND ('${priority:csv}' = '' OR i.priority::text = ANY(ARRAY[${priority:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -868,7 +868,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS \"PR Merged Size\" FROM _pr_commits_data GROUP BY 1", + "rawSql": "WITH _pr_commits_data AS (SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN commits AS c ON pr.merge_commit_sha = c.sha JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND pr.status = 'MERGED' GROUP BY 1, 2, 3) SELECT time, CAST(SUM(loc) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS \"PR Merged Size\" FROM _pr_commits_data GROUP BY 1", "refId": "A", "select": [ [ @@ -1004,7 +1004,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN NOT pr.id IN (SELECT pull_request_id FROM pull_request_comments) THEN 1 ELSE 0 END) AS \"PRs Merged w/o Review\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "SELECT CAST(pr.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr.created_date AS DATE)) - 1) END + 1) AS time, SUM(CASE WHEN NOT pr.id IN (SELECT pull_request_id FROM pull_request_comments) THEN 1 ELSE 0 END) AS \"PRs Merged w/o Review\" FROM pull_requests AS pr JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1149,7 +1149,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_cycle_time AS NUMERIC) / NULLIF(60, 0), 0) AS cycle_time /* convert null to 0 if a PR has no cycle_time to make sure cycle_time equals the sum of the four metrics below */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.created_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(cycle_time) AS \"PR Cycle Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1285,7 +1285,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(coding_time) AS \"Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_coding_time AS NUMERIC) / NULLIF(60, 0), 0) AS coding_time /* convert null to 0 if a PR has no coding_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(coding_time) AS \"Coding Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1452,7 +1452,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_pickup_time AS NUMERIC) / NULLIF(60, 0), 0) AS pickup_time /* convert null to 0 if a PR has no pickup_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(pickup_time) AS \"Pickup Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1619,7 +1619,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(review_time) AS \"Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_review_time AS NUMERIC) / NULLIF(60, 0), 0) AS review_time /* convert null to 0 if a PR has no review_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.created_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(review_time) AS \"Review Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -1786,7 +1786,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT pr.id, pr.merged_date AS pr_merged_date, COALESCE(CAST(prm.pr_deploy_time AS NUMERIC) / NULLIF(60, 0), 0) AS deploy_time /* convert null to 0 if a PR has no deploy_time to make sure cycle_time equals the sum of the four sub-metrics */ FROM pull_requests AS pr LEFT JOIN project_pr_metrics AS prm ON pr.id = prm.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.table = 'repos' WHERE $__timeFilter(pr.merged_date) AND pr.merged_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) GROUP BY 1, 2, 3) SELECT CAST(pr_merged_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(pr_merged_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(pr_merged_date AS DATE)) - 1) END + 1) AS time, AVG(deploy_time) AS \"PR Deploy Time(h)\" FROM _prs GROUP BY 1 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/git-hub.json b/grafana/dashboards/postgresql/git-hub.json index b75e629ddfb..27f929895b8 100644 --- a/grafana/dashboards/postgresql/git-hub.json +++ b/grafana/dashboards/postgresql/git-hub.json @@ -150,7 +150,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -286,7 +286,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_count AS \"Issue Count\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT i.id) AS issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_count AS \"Issue Count\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -415,7 +415,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -551,7 +551,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS open_issue_count, COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS closed_issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"open_issue_count\", closed_issue_count FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status <> 'DONE' THEN i.id ELSE NULL END) AS open_issue_count, COUNT(DISTINCT CASE WHEN status = 'DONE' THEN i.id ELSE NULL END) AS closed_issue_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and i.created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"open_issue_count\", closed_issue_count FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -659,7 +659,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE'", + "rawSql": "SELECT AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time_in_days FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND i.status = 'DONE'", "refId": "A", "select": [ [ @@ -781,7 +781,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", + "rawSql": "WITH _issues AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) END + 1) AS time, AVG(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS issue_lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND i.status = 'DONE' AND $__timeFilter(i.resolution_date) AND i.resolution_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, issue_lead_time AS \"Mean Issue Lead Time in Days\" FROM _issues ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -916,7 +916,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all outstanding bugs */ WITH _outstanding_issues AS (SELECT DISTINCT b.name AS repo_name, i.issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.status <> 'DONE') SELECT '#' || issue_key AS issue_key, title, url, queue_time_in_days FROM _outstanding_issues ORDER BY 4 DESC NULLS LAST LIMIT 20", + "rawSql": "/* Get the queue time of all outstanding bugs */ WITH _outstanding_issues AS (SELECT DISTINCT b.name AS repo_name, i.issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND i.status <> 'DONE') SELECT '#' || issue_key AS issue_key, title, url, queue_time_in_days FROM _outstanding_issues ORDER BY 4 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -1103,7 +1103,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all outstanding bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(i.created_date) AND i.status <> 'DONE' ORDER BY queue_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the queue time of all outstanding bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(i.created_date) AND i.status <> 'DONE' ORDER BY queue_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1216,7 +1216,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1366,7 +1366,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT id) AS pr_count FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND $__timeFilter(created_date) /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1505,7 +1505,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'MERGED' GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -1638,7 +1638,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1817,7 +1817,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "/* The PR/MR statuses are standardized to 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN status = 'OPEN' THEN id ELSE NULL END) AS open, COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS closed, COUNT(DISTINCT CASE WHEN status = 'MERGED' THEN id ELSE NULL END) AS merged FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, open AS \"PR: Open\", closed AS \"PR: Closed without merging\", merged AS \"PR: Closed and merged\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1921,7 +1921,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND pr.status = 'CLOSED'", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND pr.status = 'CLOSED'", "refId": "A", "select": [ [ @@ -2056,7 +2056,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1", + "rawSql": "/* The PR/MR statuses are standardized to DevLake's statuses 'OPEN', 'MERGED' and 'CLOSED'. You can check out the original status from the field \"original_status\" */ SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, CAST(COUNT(DISTINCT CASE WHEN status = 'CLOSED' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN status IN ('CLOSED', 'MERGED') THEN id ELSE NULL END), 0) AS ratio FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1", "refId": "A", "select": [ [ @@ -2164,7 +2164,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL", "refId": "A", "select": [ [ @@ -2285,7 +2285,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_merge FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_merge AS \"Time to Merge\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2391,7 +2391,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND status IN ('CLOSED', 'MERGED')", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND status IN ('CLOSED', 'MERGED')", "refId": "A", "select": [ [ @@ -2512,7 +2512,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND status IN ('CLOSED', 'MERGED') GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_close AS \"Time to Close\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(created_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(created_date AS DATE)) - 1) END + 1) AS time, AVG(CAST((EXTRACT(EPOCH FROM (closed_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) AS time_to_close FROM pull_requests WHERE $__timeFilter(created_date) AND /* Enable the following condition to remove the month with incomplete data */ /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND status IN ('CLOSED', 'MERGED') GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, time_to_close AS \"Time to Close\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -2626,7 +2626,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT COUNT(DISTINCT id) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND result = 'SUCCESS' AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2733,7 +2733,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT CAST(1.0 * COUNT(CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -2901,7 +2901,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_workflow_run_count\", failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", + "rawSql": "WITH _workflow_runs AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, COUNT(DISTINCT CASE WHEN result = 'SUCCESS' THEN id ELSE NULL END) AS successful_workflow_run_count, COUNT(DISTINCT CASE WHEN result <> 'SUCCESS' THEN id ELSE NULL END) AS failed_workflow_run_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, \"successful_workflow_run_count\", failed_workflow_run_count FROM _workflow_runs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3088,7 +3088,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", + "rawSql": "SELECT CASE WHEN result = '' THEN 'Unknown' ELSE result END AS result, COUNT(DISTINCT id) AS build_count FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH' GROUP BY 1 ORDER BY 2 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -3245,7 +3245,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", + "rawSql": "WITH _build_success_rate AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, result, id FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY \"time\", \"result\", id) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN TO_CHAR(CAST(time AS TIMESTAMP), 'MM/YYYY') ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, CAST(1.0 * SUM(CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"Workflow Runs Success Rate\" FROM _build_success_rate GROUP BY time ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3346,7 +3346,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", + "rawSql": "SELECT AVG(CAST(duration_sec AS NUMERIC) / NULLIF(60, 0)) AS duration_in_minutes FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* the following condition will remove the month with incomplete data */ finished_date >= $__timeFrom()::timestamp + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST($__timeFrom() AS DATE)) + 1) + INTERVAL '1 MONTH'", "refId": "A", "select": [ [ @@ -3501,7 +3501,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id LIKE '%github%' AND cicd_scope_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", + "rawSql": "WITH _builds AS (SELECT CAST(finished_date AS DATE) + INTERVAL '1 DAY' * (-CASE WHEN '$interval' = 'DAYOFMONTH' THEN EXTRACT(DAY FROM CAST(finished_date AS DATE)) ELSE (EXTRACT(ISODOW FROM CAST(finished_date AS DATE)) - 1) END + 1) AS time, AVG(duration_sec) AS mean_duration_sec FROM cicd_pipelines WHERE $__timeFilter(finished_date) AND id::text LIKE '%github%' AND ('${repo_id:csv}' = '' OR cicd_scope_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) /* Enable the following condition to remove the month with incomplete data */ /* and finished_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ GROUP BY 1) SELECT CASE WHEN '$interval' = 'DAYOFMONTH' THEN (TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY'))::TEXT ELSE 'W' || TO_CHAR(CAST(time AS TIMESTAMP), 'IW YYYY') END AS _time, mean_duration_sec FROM _builds ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -3601,14 +3601,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'github%'", + "definition": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'github%'", "hide": 0, "includeAll": true, "label": "Repo", "multi": true, "name": "repo_id", "options": [], - "query": "SELECT name || '--' || id AS text FROM repos WHERE id LIKE 'github%'", + "query": "SELECT name || '--' || id AS text FROM repos WHERE id::text LIKE 'github%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json b/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json index fa9e8b8090f..98d5d8cec0d 100644 --- a/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json +++ b/grafana/dashboards/postgresql/github-release-quality-and-contribution-analysis.json @@ -118,7 +118,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug distribution in major versions */ WITH bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND i.type = 'BUG') SELECT SPLIT_PART(biet.tag_name, '.', 3) || '.x' AS minor_version, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the bug distribution in major versions */ WITH bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND i.type = 'BUG') SELECT SPLIT_PART(biet.tag_name, '.', 3) || '.x' AS minor_version, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -245,7 +245,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id /* distinct new_ref_id, old_ref_id */ FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs_of_tags AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, COUNT(*) AS bug_count /* SPLIT_PART(rid.new_ref_id, ':', 3) as repo_id, */ FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' /* GROUP BY 1, 2 */ GROUP BY 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE p.base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, SPLIT_PART(rcd.new_ref_id, ':', 4) AS repo_id, pr.id AS pull_request_id, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) GROUP BY 1, 2, 3), _pr_worktype AS (SELECT DISTINCT pri.pull_request_id, i.type FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _pr_elco_and_worktype AS (SELECT ccop.tag_name, CAST(SUM(CASE WHEN pw.type = 'BUG' THEN commit_count ELSE 0 END) AS NUMERIC) / NULLIF(SUM(commit_count), 0) AS cost_percentage FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_worktype AS pw ON ccop.pull_request_id = pw.pull_request_id GROUP BY 1) SELECT bot.tag_name, bot.bug_count, peaw.cost_percentage AS \"cost_percentage(bugfixing commits/total commits)\" FROM _bugs_of_tags AS bot JOIN _pr_elco_and_worktype AS peaw ON bot.tag_name = peaw.tag_name ORDER BY 1 NULLS FIRST", + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id /* distinct new_ref_id, old_ref_id */ FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs_of_tags AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, COUNT(*) AS bug_count /* SPLIT_PART(rid.new_ref_id, ':', 3) as repo_id, */ FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' /* GROUP BY 1, 2 */ GROUP BY 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE ('${repo_id:csv}' = '' OR p.base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) UNION SELECT id, merge_commit_sha AS commit_sha FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, SPLIT_PART(rcd.new_ref_id, ':', 4) AS repo_id, pr.id AS pull_request_id, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) GROUP BY 1, 2, 3), _pr_worktype AS (SELECT DISTINCT pri.pull_request_id, i.type FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _pr_elco_and_worktype AS (SELECT ccop.tag_name, CAST(SUM(CASE WHEN pw.type = 'BUG' THEN commit_count ELSE 0 END) AS NUMERIC) / NULLIF(SUM(commit_count), 0) AS cost_percentage FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_worktype AS pw ON ccop.pull_request_id = pw.pull_request_id GROUP BY 1) SELECT bot.tag_name, bot.bug_count, peaw.cost_percentage AS \"cost_percentage(bugfixing commits/total commits)\" FROM _bugs_of_tags AS bot JOIN _pr_elco_and_worktype AS peaw ON bot.tag_name = peaw.tag_name ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -331,7 +331,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -487,7 +487,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -573,7 +573,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Get the severity distribution in bugs */ /* Get the work-type distribution in the last n tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_n_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/tags/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.type, i.title, i.description, i.severity FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_n_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_n_tags) AND i.type = 'BUG') SELECT biet.tag_name || ' ' || CASE WHEN biet.severity <> '' THEN biet.severity ELSE 'UNKNOWN' END AS severity, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -735,7 +735,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT DISTINCT b.name AS repo_name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, i.assignee_name, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) AND i.type = 'BUG' ORDER BY tag_name DESC NULLS LAST", + "rawSql": "/* Get the number of fixed bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT DISTINCT b.name AS repo_name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, i.assignee_name, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) FROM _last_5_tags) AND i.type = 'BUG' ORDER BY tag_name DESC NULLS LAST", "refId": "A", "select": [ [ @@ -822,7 +822,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT CASE WHEN component = '' THEN 'unlabeled' ELSE 'labeled' END AS component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT CASE WHEN component = '' THEN 'unlabeled' ELSE 'labeled' END AS component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet GROUP BY 1", "refId": "A", "select": [ [ @@ -909,7 +909,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet WHERE component <> '' GROUP BY 1", + "rawSql": "/* Component distribution of bugs fixed in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), bugs_in_each_tag AS (SELECT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'refs/', 1)) AS tag_name, SPLIT_PART(rid.new_ref_id, ':', 4) AS repo_id, i.issue_key, i.component, i.severity, i.title, i.description FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG') SELECT component, COUNT(*) AS bug_count FROM bugs_in_each_tag AS biet WHERE component <> '' GROUP BY 1", "refId": "A", "select": [ [ @@ -997,7 +997,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the % of contributors who fixed 80% of bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST), _bug_fixed_count AS (SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1), _bug_fixed_count_running_total AS (SELECT *, SUM(bug_fixed_count) OVER (ORDER BY bug_fixed_count DESC NULLS LAST) AS running_total FROM _bug_fixed_count), _percentile AS (SELECT pr_author, bug_fixed_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(bug_fixed_count) OVER (), 0) AS cumulative_percentage FROM _bug_fixed_count_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN pr_author ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"% of contributors who fixed 80% of the bugs\" FROM _percentile", + "rawSql": "/* Get the % of contributors who fixed 80% of bugs in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST), _bug_fixed_count AS (SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1), _bug_fixed_count_running_total AS (SELECT *, SUM(bug_fixed_count) OVER (ORDER BY bug_fixed_count DESC NULLS LAST) AS running_total FROM _bug_fixed_count), _percentile AS (SELECT pr_author, bug_fixed_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(bug_fixed_count) OVER (), 0) AS cumulative_percentage FROM _bug_fixed_count_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN pr_author ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"% of contributors who fixed 80% of the bugs\" FROM _percentile", "refId": "A", "select": [ [ @@ -1095,7 +1095,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST) SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT i.issue_key, i.type, i.severity, i.title, i.description, pr.id, pr.author_name AS pr_author, pr.created_date, RANK() OVER (PARTITION BY i.id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG' ORDER BY i.issue_key NULLS FIRST) SELECT pr_author, COUNT(*) AS bug_fixed_count FROM _bugs WHERE pr_rank = 1 GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1185,7 +1185,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the avg bug age in history */ SELECT CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "/* Get the avg bug age in history */ SELECT CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND ('${repo_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1289,7 +1289,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug age in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.id, i.lead_time_minutes FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bugs_percentile AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY tag_name ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM _bugs ORDER BY 1 NULLS FIRST), _avg_bug_age AS (SELECT tag_name, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM _bugs_percentile GROUP BY 1), _50th_bug_age AS (SELECT tag_name, CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"50th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5 GROUP BY 1) SELECT aba.*, eba.\"50th_bug_age\" FROM _avg_bug_age AS aba JOIN _50th_bug_age AS eba ON aba.tag_name = eba.tag_name", + "rawSql": "/* Get the bug age in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.id, i.lead_time_minutes FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bugs_percentile AS (SELECT *, PERCENT_RANK() OVER (PARTITION BY tag_name ORDER BY lead_time_minutes NULLS FIRST) AS percentile FROM _bugs ORDER BY 1 NULLS FIRST), _avg_bug_age AS (SELECT tag_name, CAST(AVG(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS average_bug_age FROM _bugs_percentile GROUP BY 1), _50th_bug_age AS (SELECT tag_name, CAST(MIN(lead_time_minutes) AS NUMERIC) / NULLIF(1440, 0) AS \"50th_bug_age\" FROM _bugs_percentile WHERE percentile >= 0.5 GROUP BY 1) SELECT aba.*, eba.\"50th_bug_age\" FROM _avg_bug_age AS aba JOIN _50th_bug_age AS eba ON aba.tag_name = eba.tag_name", "refId": "A", "select": [ [ @@ -1547,7 +1547,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT b.name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE SPLIT_PART(rid.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bug_age_rank AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY tag_name ORDER BY lead_time_in_days DESC NULLS LAST) AS bug_age_rank FROM _bugs) SELECT name, tag_name, issue_key, title, lead_time_in_days, url FROM _bug_age_rank WHERE bug_age_rank <= 10 ORDER BY tag_name NULLS FIRST, lead_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the bug fixer distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _bugs AS (SELECT DISTINCT b.name, REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), 'tags/', 1)) AS tag_name, i.issue_key AS issue_key, i.title, CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time_in_days, b.url || '/' || i.issue_key AS url FROM refs_issues_diffs AS rid LEFT JOIN issues AS i ON rid.issue_id = i.id LEFT JOIN pull_request_issues AS pri ON i.id = pri.issue_id JOIN boards AS b ON SPLIT_PART(rid.new_ref_id, ':', 4) = b.id WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rid.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rid.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rid.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) AND i.type = 'BUG'), _bug_age_rank AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY tag_name ORDER BY lead_time_in_days DESC NULLS LAST) AS bug_age_rank FROM _bugs) SELECT name, tag_name, issue_key, title, lead_time_in_days, url FROM _bug_age_rank WHERE bug_age_rank <= 10 ORDER BY tag_name NULLS FIRST, lead_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1696,7 +1696,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN '>=avg_bug_age' ELSE ' 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", + "rawSql": "/* Get the avg bug age in history */ WITH _avg_bug_age AS (SELECT type, AVG(lead_time_minutes) AS average_bug_age FROM issues LEFT JOIN board_issues AS bi ON issues.id = bi.issue_id WHERE type = 'BUG' AND status = 'DONE' AND ('${repo_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) GROUP BY 1), _bug_queue_time AS (SELECT i.id, abg.average_bug_age, (EXTRACT(EPOCH FROM (NOW() - created_date))/60) AS queue_time, CASE WHEN (EXTRACT(EPOCH FROM (NOW() - created_date))/60) >= average_bug_age THEN '>=avg_bug_age' ELSE ' 'DONE') SELECT distribution, COUNT(*) AS bug_count FROM _bug_queue_time GROUP BY 1", "refId": "A", "select": [ [ @@ -1858,7 +1858,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the queue time of all backlog bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(3600, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE i.type = 'BUG' AND i.status <> 'DONE' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY queue_time_in_days DESC NULLS LAST", + "rawSql": "/* Get the queue time of all backlog bugs */ SELECT b.name AS repo_name, i.issue_key AS issue_key, i.title, i.created_date, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(3600, 0) AS queue_time_in_days, b.url || '/' || i.issue_key AS url FROM issues AS i LEFT JOIN board_issues AS bi ON i.id = bi.issue_id LEFT JOIN boards AS b ON bi.board_id = b.id WHERE i.type = 'BUG' AND i.status <> 'DONE' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY queue_time_in_days DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1967,7 +1967,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS old_tag_name, COUNT(*) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2 ORDER BY 1 NULLS FIRST", + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS old_tag_name, COUNT(*) AS commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2 ORDER BY 1 NULLS FIRST", "refId": "A", "select": [ [ @@ -2078,7 +2078,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS new_tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS compared_tag_name, c.sha, c.message, c.additions, c.deletions, c.author_name FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) ORDER BY 1 DESC NULLS LAST", + "rawSql": "/* Get the bug distribution in last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 10) SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'refs/tags/', 1)) AS new_tag_name, REVERSE(SPLIT_PART(REVERSE(rcd.old_ref_id), 'refs/tags/', 1)) AS compared_tag_name, c.sha, c.message, c.additions, c.deletions, c.author_name FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (select new_ref_id from _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) ORDER BY 1 DESC NULLS LAST", "refId": "A", "select": [ [ @@ -2195,7 +2195,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2312,7 +2312,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 1), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2429,7 +2429,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE SPLIT_PART(rcd.new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", + "rawSql": "/* Get the work-type distribution in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 1 OFFSET 2), _combine_pr AS (SELECT pull_request_id AS id, commit_sha, p.pull_request_key AS pull_request_key FROM pull_request_commits LEFT JOIN pull_requests AS p ON pull_request_commits.pull_request_id = p.id WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) UNION SELECT id, merge_commit_sha, pull_request_key AS commit_sha FROM pull_requests WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))), _commit_count_of_pr AS (SELECT REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), 'tags/', 1)) AS tag_name, pr.id AS pull_request_id, COUNT(c.sha) AS pr_commit_count FROM refs_commits_diffs AS rcd LEFT JOIN commits AS c ON rcd.commit_sha = c.sha /* left join pull_request_commits prc on c.sha = prc.commit_sha */ LEFT JOIN _combine_pr AS pr ON c.sha = pr.commit_sha WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(rcd.new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND /* and rcd.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ REVERSE(SPLIT_PART(REVERSE(rcd.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1, 2), _pr_issues AS (SELECT pri.pull_request_id, pri.issue_id, i.issue_key, i.type, ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY pr.created_date ASC NULLS FIRST) AS pr_rank FROM pull_request_issues AS pri LEFT JOIN pull_requests AS pr ON pri.pull_request_id = pr.id LEFT JOIN issues AS i ON pri.issue_id = i.id WHERE i.issue_key <> '0'), _final_results AS (SELECT DISTINCT ccop.*, pi.type FROM _commit_count_of_pr AS ccop LEFT JOIN _pr_issues AS pi ON ccop.pull_request_id = pi.pull_request_id WHERE pi.pr_rank = 1 ORDER BY 1 NULLS FIRST) SELECT tag_name, CASE WHEN type <> '' THEN type ELSE 'UNKNOWN' END AS type, SUM(pr_commit_count) AS commit_count FROM _final_results GROUP BY 1, 2", "refId": "A", "select": [ [ @@ -2517,7 +2517,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "/* Get each contributor's work in bugfixing in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5), _author_commits AS (SELECT c.author_name, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1), _author_commits_running_total AS (SELECT *, SUM(commit_count) OVER (ORDER BY commit_count DESC NULLS LAST) AS running_total FROM _author_commits), _percentile AS (SELECT author_name, commit_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(commit_count) OVER (), 0) AS cumulative_percentage FROM _author_commits_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN author_name ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"contributors who contributed 80% of dev_eq\" FROM _percentile", + "rawSql": "/* Get each contributor's work in bugfixing in the last 5 tags */ WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5), _author_commits AS (SELECT c.author_name, COUNT(c.sha) AS commit_count FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1), _author_commits_running_total AS (SELECT *, SUM(commit_count) OVER (ORDER BY commit_count DESC NULLS LAST) AS running_total FROM _author_commits), _percentile AS (SELECT author_name, commit_count, CAST(running_total AS NUMERIC) / NULLIF(SUM(commit_count) OVER (), 0) AS cumulative_percentage FROM _author_commits_running_total) SELECT CAST(COUNT(CASE WHEN cumulative_percentage <= 0.8 THEN author_name ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(*), 0) AS \"contributors who contributed 80% of dev_eq\" FROM _percentile", "refId": "A", "select": [ [ @@ -2612,7 +2612,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE SPLIT_PART(new_ref_id, ':', 4)::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT c.author_name, COUNT(c.sha) AS total_dev_eq FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", + "rawSql": "WITH refs_commits_diffs AS (SELECT new_refs.id AS new_ref_id, old_refs.id AS old_ref_id, commits_diffs.commit_sha, new_commit_sha, old_commit_sha FROM commits_diffs LEFT JOIN refs AS new_refs ON new_refs.commit_sha = commits_diffs.new_commit_sha LEFT JOIN refs AS old_refs ON old_refs.commit_sha = commits_diffs.old_commit_sha), _last_5_tags AS (SELECT DISTINCT /* distinct new_ref_id, old_ref_id */ REVERSE(SPLIT_PART(REVERSE(new_ref_id), ':', 1)) AS new_ref_id, REVERSE(SPLIT_PART(REVERSE(old_ref_id), ':', 1)) AS old_ref_id FROM refs_commits_diffs WHERE ('${repo_id:csv}' = '' OR SPLIT_PART(new_ref_id, ':', 4) = ANY(ARRAY[${repo_id:singlequote}]::text[])) ORDER BY 1 DESC NULLS LAST LIMIT 5) SELECT c.author_name, COUNT(c.sha) AS total_dev_eq FROM refs_commits_diffs AS rcf LEFT JOIN commits AS c ON rcf.commit_sha = c.sha WHERE SPLIT_PART(rcf.new_ref_id, ':', 4) /* rcf.new_ref_id in (SELECT new_ref_id FROM _last_5_tags) */ IN (${repo_id}) AND REVERSE(SPLIT_PART(REVERSE(rcf.new_ref_id), ':', 1)) IN (SELECT new_ref_id FROM _last_5_tags) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/pager-duty.json b/grafana/dashboards/postgresql/pager-duty.json index 751629fbc6c..437ecf1fd29 100644 --- a/grafana/dashboards/postgresql/pager-duty.json +++ b/grafana/dashboards/postgresql/pager-duty.json @@ -175,7 +175,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -278,7 +278,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -398,7 +398,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT b.name AS service, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS lead_time_days, i.url FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT b.name AS service, i.issue_key, i.description, i.original_status, i.priority, i.created_date, i.updated_date, ROUND(CAST((CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS DECIMAL), 1) AS lead_time_days, i.url FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -505,7 +505,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", + "rawSql": "WITH _requirements AS (SELECT COUNT(DISTINCT i.id) AS total_count, COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS resolved_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT NOW() AS time, CAST(1.0 * resolved_count AS NUMERIC) / NULLIF(total_count, 0) AS requirement_delivery_rate FROM _requirements", "refId": "A", "select": [ [ @@ -639,7 +639,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.created_date AS DATE)) + 1) AS time, CAST(1.0 * COUNT(DISTINCT CASE WHEN i.original_status = 'resolved' THEN i.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS resolved_rate FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT time, resolved_rate FROM _requirements ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -773,7 +773,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -882,7 +882,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", + "rawSql": "WITH _ranks AS (SELECT i.lead_time_minutes, PERCENT_RANK() OVER (ORDER BY lead_time_minutes ASC NULLS FIRST) AS ranks FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))) SELECT MAX(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS value FROM _ranks WHERE ranks <= 0.8", "refId": "A", "select": [ [ @@ -1021,7 +1021,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", + "rawSql": "WITH _requirements AS (SELECT CAST(i.resolution_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(i.resolution_date AS DATE)) + 1) AS time, AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS mean_incident_age FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, mean_incident_age FROM _requirements ORDER BY time ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1123,7 +1123,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND bi.board_id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", + "rawSql": "WITH _ranks AS (SELECT ROUND(CAST(i.lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) AS lead_time_day FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id WHERE i.original_status = 'resolved' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR bi.board_id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY lead_time_day ASC NULLS FIRST) SELECT NOW() AS time, LPAD(lead_time_day || 'd', 4, ' ') AS metric, PERCENT_RANK() OVER (ORDER BY lead_time_day ASC NULLS FIRST) AS value FROM _ranks ORDER BY lead_time_day ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -1266,14 +1266,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT name || '--' || id FROM boards WHERE id LIKE 'pagerduty%'", + "definition": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'pagerduty%'", "hide": 0, "includeAll": true, "label": "Choose Board", "multi": true, "name": "board_id", "options": [], - "query": "SELECT name || '--' || id FROM boards WHERE id LIKE 'pagerduty%'", + "query": "SELECT name || '--' || id FROM boards WHERE id::text LIKE 'pagerduty%'", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/q-dev-dora.json b/grafana/dashboards/postgresql/q-dev-dora.json index b7f82438a6c..ff077e0c5f9 100644 --- a/grafana/dashboards/postgresql/q-dev-dora.json +++ b/grafana/dashboards/postgresql/q-dev-dora.json @@ -316,7 +316,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS \"Deployments\" FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", + "rawSql": "SELECT COUNT(DISTINCT cdc.cicd_deployment_id) AS \"Deployments\" FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date)", "refId": "A" } ], @@ -383,7 +383,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) AS \"Lead Time (hours)\" FROM _median_ranks WHERE ranks <= 0.5", + "rawSql": "WITH _pr_stats AS (SELECT DISTINCT pr.id, ppm.pr_cycle_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date)), _median_ranks AS (SELECT *, PERCENT_RANK() OVER (ORDER BY pr_cycle_time NULLS FIRST) AS ranks FROM _pr_stats) SELECT ROUND(CAST(CAST(MAX(pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS DECIMAL), 1) AS \"Lead Time (hours)\" FROM _median_ranks WHERE ranks <= 0.5", "refId": "A" } ], @@ -450,7 +450,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS \"Change Failure Rate\" FROM _failure_caused", + "rawSql": "WITH _deployments AS (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()), _failure_caused AS (SELECT d.deployment_id, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM _deployments AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1) SELECT CASE WHEN COUNT(deployment_id) = 0 THEN NULL ELSE CAST(SUM(has_incident) AS NUMERIC) / NULLIF(COUNT(deployment_id), 0) END AS \"Change Failure Rate\" FROM _failure_caused", "refId": "A" } ], @@ -601,7 +601,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), lead_time_monthly AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS \"AI Accepted Lines\", ROUND(lt.avg_lead_time, 1) AS \"Median Lead Time (hours)\" FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_monthly AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), lead_time_monthly AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM pull_requests AS pr JOIN project_pr_metrics AS ppm ON ppm.id = pr.id JOIN project_mapping AS pm ON pr.base_repo_id = pm.row_id AND pm.\"table\" = 'repos' JOIN cicd_deployment_commits AS cdc ON ppm.deployment_commit_id = cdc.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND NOT pr.merged_date IS NULL AND NOT ppm.pr_cycle_time IS NULL AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, lt.month), '%Y-%m-%d') AS time, ai.ai_lines AS \"AI Accepted Lines\", ROUND(lt.avg_lead_time, 1) AS \"Median Lead Time (hours)\" FROM ai_monthly AS ai LEFT JOIN lead_time_monthly AS lt ON ai.month = lt.month WHERE NOT ai.month IS NULL OR NOT lt.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -743,7 +743,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), deployment_count AS (SELECT TO_CHAR(CAST(MAX(cdc.finished_date) AS TIMESTAMP), 'YYYY-MM-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS \"AI Acceptance Rate\", dc.deploy_count AS \"Deployment Count\" FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_acceptance AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), deployment_count AS (SELECT TO_CHAR(CAST(MAX(cdc.finished_date) AS TIMESTAMP), 'YYYY-MM-01') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deploy_count FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, dc.month), '%Y-%m-%d') AS time, ai.acceptance_rate AS \"AI Acceptance Rate\", dc.deploy_count AS \"Deployment Count\" FROM ai_acceptance AS ai LEFT JOIN deployment_count AS dc ON ai.month = dc.month WHERE NOT ai.month IS NULL OR NOT dc.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -885,7 +885,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), cfr_monthly AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS \"AI Generated Tests\", cfr.cfr AS \"Change Failure Rate\" FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", + "rawSql": "WITH ai_tests AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01') AS month, SUM(test_generation_generated_tests) AS generated_tests FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM-01')), cfr_monthly AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM-01')) SELECT TO_DATE(COALESCE(ai.month, cfr.month), '%Y-%m-%d') AS time, ai.generated_tests AS \"AI Generated Tests\", cfr.cfr AS \"Change Failure Rate\" FROM ai_tests AS ai LEFT JOIN cfr_monthly AS cfr ON ai.month = cfr.month WHERE NOT ai.month IS NULL OR NOT cfr.month IS NULL ORDER BY time NULLS FIRST", "refId": "A" } ], @@ -1175,7 +1175,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM')), dora_metrics AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM')), cfr_metrics AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE pm.project_name::text IN (SELECT v FROM unnest(CASE WHEN '${project}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project}]::text[] END) v) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS \"Month\", COALESCE(ai.active_users, 0) AS \"Q Dev Users\", COALESCE(ai.ai_lines, 0) AS \"AI Code Lines\", ai.acceptance_rate AS \"AI Acceptance Rate\", COALESCE(ai.generated_tests, 0) AS \"AI Tests\", COALESCE(ai.review_findings, 0) AS \"Review Findings\", COALESCE(dm.deployments, 0) AS \"Deployments\", ROUND(dm.avg_lead_time, 1) AS \"Lead Time (hours)\", cfr.cfr AS \"Change Failure Rate\" FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", + "rawSql": "WITH ai_metrics AS (SELECT TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS active_users, SUM(inline_ai_code_lines + chat_ai_code_lines) AS ai_lines, CAST(SUM(inline_acceptance_count) AS NUMERIC) / NULLIF(NULLIF(SUM(inline_suggestions_count), 0), 0) AS acceptance_rate, SUM(test_generation_generated_tests) AS generated_tests, SUM(code_review_findings_count) AS review_findings FROM _tool_q_dev_user_data WHERE $__timeFilter(date) GROUP BY TO_CHAR(CAST(date AS TIMESTAMP), 'YYYY-MM')), dora_metrics AS (SELECT TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM') AS month, COUNT(DISTINCT cdc.cicd_deployment_id) AS deployments, CAST(AVG(ppm.pr_cycle_time) AS NUMERIC) / NULLIF(60, 0) AS avg_lead_time FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' LEFT JOIN cicd_deployment_commits AS cdc2 ON cdc.cicd_deployment_id = cdc2.cicd_deployment_id LEFT JOIN project_pr_metrics AS ppm ON ppm.deployment_commit_id = cdc2.id WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' AND $__timeFilter(cdc.finished_date) GROUP BY TO_CHAR(CAST(cdc.finished_date AS TIMESTAMP), 'YYYY-MM')), cfr_metrics AS (SELECT TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM') AS month, CAST(SUM(has_incident) AS NUMERIC) / NULLIF(NULLIF(COUNT(deployment_id), 0), 0) AS cfr FROM (SELECT d.deployment_id, d.deployment_finished_date, COUNT(DISTINCT CASE WHEN NOT i.id IS NULL THEN d.deployment_id ELSE NULL END) AS has_incident FROM (SELECT cdc.cicd_deployment_id AS deployment_id, MAX(cdc.finished_date) AS deployment_finished_date FROM cicd_deployment_commits AS cdc JOIN project_mapping AS pm ON cdc.cicd_scope_id = pm.row_id AND pm.\"table\" = 'cicd_scopes' WHERE ('${project:csv}' = '' OR pm.project_name::text = ANY(ARRAY[${project:singlequote}]::text[])) AND cdc.result = 'SUCCESS' AND cdc.environment = 'PRODUCTION' GROUP BY 1 HAVING MAX(cdc.finished_date) BETWEEN $__timeFrom() AND $__timeTo()) AS d LEFT JOIN project_incident_deployment_relationships AS pim ON d.deployment_id = pim.deployment_id LEFT JOIN incidents AS i ON pim.id = i.id GROUP BY 1, 2) AS failure_data GROUP BY TO_CHAR(CAST(deployment_finished_date AS TIMESTAMP), 'YYYY-MM')) SELECT COALESCE(ai.month, dm.month, cfr.month) AS \"Month\", COALESCE(ai.active_users, 0) AS \"Q Dev Users\", COALESCE(ai.ai_lines, 0) AS \"AI Code Lines\", ai.acceptance_rate AS \"AI Acceptance Rate\", COALESCE(ai.generated_tests, 0) AS \"AI Tests\", COALESCE(ai.review_findings, 0) AS \"Review Findings\", COALESCE(dm.deployments, 0) AS \"Deployments\", ROUND(dm.avg_lead_time, 1) AS \"Lead Time (hours)\", cfr.cfr AS \"Change Failure Rate\" FROM ai_metrics AS ai LEFT JOIN dora_metrics AS dm ON ai.month = dm.month LEFT JOIN cfr_metrics AS cfr ON ai.month = cfr.month ORDER BY ai.month DESC NULLS LAST", "refId": "A" } ], diff --git a/grafana/dashboards/postgresql/sonar-qube-cloud.json b/grafana/dashboards/postgresql/sonar-qube-cloud.json index ff5b9547eef..6719a3e2d7d 100644 --- a/grafana/dashboards/postgresql/sonar-qube-cloud.json +++ b/grafana/dashboards/postgresql/sonar-qube-cloud.json @@ -152,7 +152,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'SECURITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ('${project_id:csv}' = '' OR ci.project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND cii.software_quality = 'SECURITY' AND ('${severity:csv}' = '' OR ci.severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -240,7 +240,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'RELIABILITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ('${project_id:csv}' = '' OR ci.project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND cii.software_quality = 'RELIABILITY' AND ('${severity:csv}' = '' OR ci.severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -328,7 +328,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ci.project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND cii.software_quality = 'MAINTAINABILITY' AND ci.severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT ci.id) FROM cq_issues AS ci JOIN cq_issue_impacts AS cii ON ci.id = cii.cq_issue_id WHERE ('${project_id:csv}' = '' OR ci.project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND cii.software_quality = 'MAINTAINABILITY' AND ('${severity:csv}' = '' OR ci.severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -430,7 +430,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Security Hotspots\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'HOTSPOTS' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -519,7 +519,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'HOTSPOTS' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST(CAST(COUNT(CASE WHEN status <> 'TO_REVIEW' THEN id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT id), 0) * 100 AS DECIMAL), 2) || '%' AS \"Reviewed\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'HOTSPOTS' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -634,7 +634,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST((SUM(lines_to_cover) - SUM(uncovered_lines)) AS NUMERIC) / NULLIF(SUM(lines_to_cover), 0) * 100, 1) || '% ' || 'Coverage on ' || ROUND(CAST(CAST(SUM(lines_to_cover) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines to cover' FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -724,7 +724,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT id) AS \"Code Smells\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'CODE_SMELL' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -814,7 +814,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) AND type = 'CODE_SMELL' AND severity::text IN (SELECT v FROM unnest(CASE WHEN '${severity}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${severity}]::text[] END) v)", + "rawSql": "SELECT FLOOR(CAST(CAST(SUM(debt) AS NUMERIC) / NULLIF(8, 0) AS NUMERIC) / NULLIF(60, 0)) || ' day(s) ' || FLOOR(CAST((SUM(debt) % 480) AS NUMERIC) / NULLIF(60, 0)) || ' hour(s) ' AS \"Debt\" FROM cq_issues WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) AND type = 'CODE_SMELL' AND ('${severity:csv}' = '' OR severity::text = ANY(ARRAY[${severity:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -929,7 +929,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT SUM(duplicated_blocks) FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1018,7 +1018,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT ROUND(CAST(SUM(duplicated_lines) AS NUMERIC) / NULLIF(SUM(num_of_lines), 0) * 100, 1) || '% ' || 'Duplications on ' || ROUND(CAST(CAST(SUM(ncloc) AS NUMERIC) / NULLIF(1000, 0) AS DECIMAL), 0) || 'k Lines' FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1121,7 +1121,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(ncloc) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT SUM(ncloc) FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1211,7 +1211,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT SUM(num_of_lines) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT SUM(num_of_lines) FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1301,7 +1301,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT file_path) FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT file_path) FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[]))", "refId": "A", "sql": { "columns": [ @@ -1405,7 +1405,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE project_key::text IN (SELECT v FROM unnest(CASE WHEN '${project_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${project_id}]::text[] END) v) ORDER BY bugs DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT file_name, num_of_lines AS \"Lines of Code\", bugs AS \"Bugs\", vulnerabilities AS \"Vulnerabilities\", code_smells AS \"Code Smells\", security_hotspots AS \"Security Hotspots\", ROUND(coverage, 2) || '%' AS \"Coverage\", ROUND(duplicated_lines_density, 2) || '%' AS \"Duplications\" FROM cq_file_metrics WHERE ('${project_id:csv}' = '' OR project_key::text = ANY(ARRAY[${project_id:singlequote}]::text[])) ORDER BY bugs DESC NULLS LAST LIMIT 20", "refId": "A", "sql": { "columns": [ @@ -1474,14 +1474,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", + "definition": "SELECT DISTINCT severity FROM cq_issues WHERE id::text LIKE 'sonar%'", "hide": 0, "includeAll": true, "label": "Severity", "multi": true, "name": "severity", "options": [], - "query": "SELECT DISTINCT severity FROM cq_issues WHERE id LIKE 'sonar%'", + "query": "SELECT DISTINCT severity FROM cq_issues WHERE id::text LIKE 'sonar%'", "refresh": 1, "regex": "", "skipUrlSync": false, diff --git a/grafana/dashboards/postgresql/weekly-bug-retro.json b/grafana/dashboards/postgresql/weekly-bug-retro.json index 10b3026e897..4ca4ca2c6aa 100644 --- a/grafana/dashboards/postgresql/weekly-bug-retro.json +++ b/grafana/dashboards/postgresql/weekly-bug-retro.json @@ -164,7 +164,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly New Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly New Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -302,7 +302,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly Closed Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", + "rawSql": "WITH bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST) SELECT TO_CHAR(CAST(cw.start_of_week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(cw.start_of_week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS \"Weekly Closed Bugs\" FROM calendar_weeks AS cw LEFT JOIN bugs AS b ON cw.start_of_week = b.time ORDER BY cw.start_of_week ASC NULLS FIRST", "refId": "A", "select": [ [ @@ -442,7 +442,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST), created_bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), resolved_bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), weekly_new_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_new_bug FROM calendar_weeks AS cw LEFT JOIN created_bugs AS cb ON cw.start_of_week = cb.time), weekly_closed_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_closed_bug FROM calendar_weeks AS cw LEFT JOIN resolved_bugs AS cb ON cw.start_of_week = cb.time), weekly_updates AS (SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 LEFT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week UNION SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 RIGHT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week), original_open_bugs AS (SELECT COUNT(DISTINCT i.id) AS original_open_bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND i.created_date < $__timeFrom() AND (i.status <> 'DONE' OR $__timeFilter(i.resolution_date)) AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)), weekly_updated_without_null AS (SELECT week, COALESCE(weekly_new_bug, 0) AS weekly_new_bug, COALESCE(weekly_closed_bug, 0) AS weekly_closed_bug, original_open_bug_count FROM weekly_updates, original_open_bugs WHERE NOT week IS NULL), weekly_delta AS (SELECT *, (weekly_new_bug - weekly_closed_bug) AS weekly_delta FROM weekly_updated_without_null ORDER BY week ASC NULLS FIRST), final_data AS (SELECT *, TO_CHAR(CAST(week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS _week, SUM(weekly_delta) OVER (ORDER BY week ASC NULLS FIRST) AS weekly_accumulated FROM weekly_delta) SELECT _week, (original_open_bug_count + weekly_accumulated) AS \"Total No. of Outstanding Bugs By the End of Week\" FROM final_data", + "rawSql": "WITH calendar_date AS (SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') >= ($__timeTo()::timestamp - INTERVAL '6 MONTH')), calendar_weeks AS (SELECT DISTINCT CAST(CAST(d AS DATE) + -(EXTRACT(ISODOW FROM CAST(d AS DATE)) - 1) * INTERVAL '1 day' AS DATE) AS start_of_week FROM calendar_date ORDER BY 1 ASC NULLS FIRST), created_bugs AS (SELECT CAST(i.created_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.created_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND $__timeFilter(i.created_date) AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), resolved_bugs AS (SELECT CAST(i.resolution_date AS DATE) + -(EXTRACT(ISODOW FROM CAST(i.resolution_date AS DATE)) - 1) * INTERVAL '1 day' AS time, COUNT(DISTINCT i.id) AS bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND $__timeFilter(i.resolution_date) AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) GROUP BY \"time\" ORDER BY time DESC NULLS LAST), weekly_new_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_new_bug FROM calendar_weeks AS cw LEFT JOIN created_bugs AS cb ON cw.start_of_week = cb.time), weekly_closed_bug AS (SELECT cw.start_of_week AS week, CASE WHEN NOT bug_count IS NULL THEN bug_count ELSE 0 END AS weekly_closed_bug FROM calendar_weeks AS cw LEFT JOIN resolved_bugs AS cb ON cw.start_of_week = cb.time), weekly_updates AS (SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 LEFT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week UNION SELECT t1.week, weekly_new_bug, weekly_closed_bug FROM weekly_new_bug AS t1 RIGHT JOIN weekly_closed_bug AS t2 ON t1.week = t2.week), original_open_bugs AS (SELECT COUNT(DISTINCT i.id) AS original_open_bug_count FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND i.created_date < $__timeFrom() AND (i.status <> 'DONE' OR $__timeFilter(i.resolution_date)) AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))), weekly_updated_without_null AS (SELECT week, COALESCE(weekly_new_bug, 0) AS weekly_new_bug, COALESCE(weekly_closed_bug, 0) AS weekly_closed_bug, original_open_bug_count FROM weekly_updates, original_open_bugs WHERE NOT week IS NULL), weekly_delta AS (SELECT *, (weekly_new_bug - weekly_closed_bug) AS weekly_delta FROM weekly_updated_without_null ORDER BY week ASC NULLS FIRST), final_data AS (SELECT *, TO_CHAR(CAST(week AS TIMESTAMP), 'MM/DD') || ' - ' || TO_CHAR(CAST(week + INTERVAL '7 DAY' AS TIMESTAMP), 'MM/DD') AS _week, SUM(weekly_delta) OVER (ORDER BY week ASC NULLS FIRST) AS weekly_accumulated FROM weekly_delta) SELECT _week, (original_open_bug_count + weekly_accumulated) AS \"Total No. of Outstanding Bugs By the End of Week\" FROM final_data", "refId": "A", "select": [ [ @@ -564,7 +564,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -676,7 +676,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS \"Issue Number\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' GROUP BY 1", + "rawSql": "SELECT priority, COUNT(DISTINCT i.id) AS \"Issue Number\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' GROUP BY 1", "refId": "A", "select": [ [ @@ -835,7 +835,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", i.url AS \"Url\", i.creator_name AS \"Creator\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", i.url AS \"Url\", i.creator_name AS \"Creator\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.created_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -944,7 +944,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1058,7 +1058,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", + "rawSql": "SELECT AVG(CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1231,7 +1231,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS \"Lead Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS \"Lead Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day'", "refId": "A", "select": [ [ @@ -1371,7 +1371,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT '#' || i.issue_key || ' ' || i.title AS issue_key, CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND status = 'DONE' AND i.status <> 'REJECTED' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' ORDER BY lead_time DESC NULLS LAST LIMIT 10", + "rawSql": "SELECT '#' || i.issue_key || ' ' || i.title AS issue_key, CAST(lead_time_minutes AS NUMERIC) / NULLIF(1440, 0) AS lead_time FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND status = 'DONE' AND i.status <> 'REJECTED' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND i.resolution_date BETWEEN TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - INTERVAL '1 DAY' * ((EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) + 7) AND TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0)) - (EXTRACT(ISODOW FROM TO_TIMESTAMP(CAST($__to AS NUMERIC) / NULLIF(1000, 0))) - 1) * INTERVAL '1 day' ORDER BY lead_time DESC NULLS LAST LIMIT 10", "refId": "A", "select": [ [ @@ -1480,7 +1480,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND NOT i.status IN ('DONE', 'REJECTED') AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1588,7 +1588,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT AVG(CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v)", + "rawSql": "SELECT AVG(CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND NOT i.status IN ('DONE', 'REJECTED') AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1768,7 +1768,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) AND priority::text IN (SELECT v FROM unnest(CASE WHEN '${priority}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${priority}]::text[] END) v) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND NOT i.status IN ('DONE', 'REJECTED') AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) AND ('${priority:csv}' = '' OR priority::text = ANY(ARRAY[${priority:singlequote}]::text[])) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ @@ -1908,7 +1908,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT '#' || i.issue_key AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY 2 DESC NULLS LAST LIMIT 100", + "rawSql": "SELECT '#' || i.issue_key AS issue_key, CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND NOT i.status IN ('DONE', 'REJECTED') AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY 2 DESC NULLS LAST LIMIT 100", "refId": "A", "select": [ [ @@ -2105,7 +2105,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${board_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${board_id}]::text[] END) v) ORDER BY 'Queue Time' DESC NULLS LAST", + "rawSql": "SELECT i.issue_key AS \"Issue Number\", i.title AS \"Title\", priority AS \"Priority\", severity AS \"Severity\", CAST(((EXTRACT(EPOCH FROM (NOW() - i.created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"Queue Time in Days\", i.url AS \"Url\" FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND NOT i.status IN ('DONE', 'REJECTED') AND i.assignee_name = '' AND ('${board_id:csv}' = '' OR b.id::text = ANY(ARRAY[${board_id:singlequote}]::text[])) ORDER BY 'Queue Time' DESC NULLS LAST", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/weekly-community-retro.json b/grafana/dashboards/postgresql/weekly-community-retro.json index 4eac61915f2..4bb53f118f6 100644 --- a/grafana/dashboards/postgresql/weekly-community-retro.json +++ b/grafana/dashboards/postgresql/weekly-community-retro.json @@ -147,7 +147,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -254,7 +254,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT COUNT(DISTINCT i.id) FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -362,7 +362,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(SUM(CASE WHEN NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS community_issue_ratio FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[]))) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT i.id), 0) AS community_issue_ratio FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -472,7 +472,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT DISTINCT i.creator_name FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_name IN (SELECT DISTINCT creator_name FROM issues WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT creator_name IS NULL)", + "rawSql": "SELECT DISTINCT i.creator_name FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_name IN (SELECT DISTINCT creator_name FROM issues WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT creator_name IS NULL)", "refId": "A", "select": [ [ @@ -566,7 +566,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT 1 - CAST(COUNT(DISTINCT CASE WHEN comment_id IS NULL THEN issue_id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT issue_id), 0) AS response_rate FROM issue_comment_list", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))) SELECT 1 - CAST(COUNT(DISTINCT CASE WHEN comment_id IS NULL THEN issue_id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT issue_id), 0) AS response_rate FROM issue_comment_list", "refId": "A", "select": [ [ @@ -736,7 +736,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"queue_time_in_days\", url FROM issue_comment_list WHERE comment_id IS NULL", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, i.status, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))) SELECT issue_key, title, creator_name, issue_created_date, status, CAST(((EXTRACT(EPOCH FROM (NOW() - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS \"queue_time_in_days\", url FROM issue_comment_list WHERE comment_id IS NULL", "refId": "A", "select": [ [ @@ -846,7 +846,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT i.id AS issue_id, i.url, i.title, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))) SELECT AVG(CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0)) FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -1020,7 +1020,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "WITH issue_comment_list AS (SELECT REVERSE(SPLIT_PART(REVERSE(i.url), '/', 1)) AS issue_number, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE i.type::text IN (SELECT v FROM unnest(CASE WHEN '${issue_type}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${issue_type}]::text[] END) v) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND b.id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))) SELECT issue_key, title, creator_name /* body, */, issue_created_date, comment_date, CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS response_time_in_days, url FROM issue_comment_list WHERE comment_rank = 1", + "rawSql": "WITH issue_comment_list AS (SELECT REVERSE(SPLIT_PART(REVERSE(i.url), '/', 1)) AS issue_number, i.url, i.issue_key, i.title, i.creator_name, i.created_date AS issue_created_date, ic.id AS comment_id, ic.created_date AS comment_date, ic.body, CASE WHEN NOT ic.id IS NULL THEN RANK() OVER (PARTITION BY i.id ORDER BY ic.created_date ASC NULLS FIRST) ELSE NULL END AS comment_rank FROM issues AS i JOIN board_issues AS bi ON i.id = bi.issue_id JOIN boards AS b ON bi.board_id = b.id LEFT JOIN issue_comments AS ic ON i.id = ic.issue_id WHERE ('${issue_type:csv}' = '' OR i.type::text = ANY(ARRAY[${issue_type:singlequote}]::text[])) AND CAST(i.created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR b.id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT i.creator_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))) SELECT issue_key, title, creator_name /* body, */, issue_created_date, comment_date, CAST(((EXTRACT(EPOCH FROM (comment_date - issue_created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS response_time_in_days, url FROM issue_comment_list WHERE comment_rank = 1", "refId": "A", "select": [ [ @@ -1153,7 +1153,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1260,7 +1260,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -1367,7 +1367,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT CAST(SUM(CASE WHEN NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS community_pr_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v)", + "rawSql": "SELECT CAST(SUM(CASE WHEN NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[]))) THEN 1 ELSE 0 END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS community_pr_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[]))", "refId": "A", "select": [ [ @@ -1474,7 +1474,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -1581,7 +1581,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT AVG(CAST((EXTRACT(EPOCH FROM (merged_date - created_date))/60) AS NUMERIC) / NULLIF(1440, 0)) FROM pull_requests WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -1675,7 +1675,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT DISTINCT author_name FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND NOT merged_date IS NULL AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_name IN (SELECT DISTINCT author_name FROM pull_requests WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT author_name IS NULL)", + "rawSql": "SELECT DISTINCT author_name FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND NOT merged_date IS NULL AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT author_name IN (SELECT DISTINCT author_name FROM pull_requests WHERE created_date < CURRENT_DATE - INTERVAL '1 day' AND NOT author_name IS NULL)", "refId": "A", "select": [ [ @@ -1787,7 +1787,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS merged_pull_request_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT CAST(COUNT(DISTINCT CASE WHEN NOT merged_date IS NULL THEN pr.id ELSE NULL END) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr.id), 0) AS merged_pull_request_ratio FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -1926,7 +1926,7 @@ "metricColumn": "none", "queryType": "randomWalk", "rawQuery": true, - "rawSql": "SELECT pull_request_key, title, status, author_name, created_date, CAST(((EXTRACT(EPOCH FROM (CURRENT_DATE - created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, url FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v))", + "rawSql": "SELECT pull_request_key, title, status, author_name, created_date, CAST(((EXTRACT(EPOCH FROM (CURRENT_DATE - created_date))/60)) AS NUMERIC) / NULLIF(1440, 0) AS queue_time_in_days, url FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[])))", "refId": "A", "select": [ [ @@ -2064,7 +2064,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS pull_request_count FROM pull_requests AS pr WHERE CAST(created_date AS DATE) BETWEEN CURRENT_DATE - INTERVAL '1 day' AND CURRENT_DATE - (EXTRACT(ISODOW FROM CURRENT_DATE) - 1) * INTERVAL '1 day' AND ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL AND NOT author_id IN (SELECT DISTINCT id FROM accounts WHERE ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[]))) GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ @@ -2166,7 +2166,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT COUNT(DISTINCT author_id) AS all_contributor_count FROM pull_requests AS pr WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) */", + "rawSql": "SELECT COUNT(DISTINCT author_id) AS all_contributor_count FROM pull_requests AS pr WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[]))) */", "refId": "A", "select": [ [ @@ -2273,7 +2273,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE base_repo_id::text IN (SELECT v FROM unnest(CASE WHEN '${repo_id}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${repo_id}]::text[] END) v) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where organization::text IN (SELECT v FROM unnest(CASE WHEN '${org}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[${org}]::text[] END) v)) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", + "rawSql": "SELECT author_name, COUNT(DISTINCT pr.id) AS merged_pull_request_count FROM pull_requests AS pr WHERE ('${repo_id:csv}' = '' OR base_repo_id::text = ANY(ARRAY[${repo_id:singlequote}]::text[])) AND NOT merged_date IS NULL /* and author_id not in (select distinct id from accounts where ('${org:csv}' = '' OR organization::text = ANY(ARRAY[${org:singlequote}]::text[]))) */ GROUP BY 1 ORDER BY 2 DESC NULLS LAST LIMIT 20", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/postgresql/work-logs.json b/grafana/dashboards/postgresql/work-logs.json index 86949c30b3d..8ff5fabc6bf 100644 --- a/grafana/dashboards/postgresql/work-logs.json +++ b/grafana/dashboards/postgresql/work-logs.json @@ -216,7 +216,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(resolution_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(merged_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN 'Date' ELSE NULL END AS \"Date\", 'Time', \"Activity\", \"Details\", \"Name\" FROM _activities", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(resolution_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT TO_CHAR(CAST(merged_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'YYYY-MM-DD | FMDay') AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST) SELECT CASE WHEN _row_number = 1 THEN 'Date' ELSE NULL END AS \"Date\", 'Time', \"Activity\", \"Details\", \"Name\" FROM _activities", "refId": "A", "sql": { "columns": [ @@ -359,7 +359,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activity_count_per_day AS (SELECT \"Date\", COUNT(*) AS value FROM _activities GROUP BY 1), last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'wIW YYYY') AS week_name, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'YYYYIW') AS week_number FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _calendar_months_with_rank AS (SELECT d, week_name || ' (' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' AS TIMESTAMP), 'MM/DD') || ' ~ ' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') || ')' AS week_name, \"week_number\", TO_CHAR(CAST(d AS TIMESTAMP), 'FMDay') AS weekday, DENSE_RANK() OVER (ORDER BY week_number DESC NULLS LAST) AS week_rank FROM last_few_calendar_months ORDER BY 1 DESC NULLS LAST), _final_dataset AS (SELECT _calendar_months_with_rank.*, CASE WHEN _activity_count_per_day.value IS NULL THEN 0 ELSE _activity_count_per_day.value END AS activity_count FROM _calendar_months_with_rank LEFT JOIN _activity_count_per_day ON _calendar_months_with_rank.d = _activity_count_per_day.\"Date\"), WeekSummary AS (SELECT week_name, SUM(CASE WHEN weekday = 'Monday' THEN activity_count END) AS \"Mon\", SUM(CASE WHEN weekday = 'Tuesday' THEN activity_count END) AS \"Tue\", SUM(CASE WHEN weekday = 'Wednesday' THEN activity_count END) AS \"Wed\", SUM(CASE WHEN weekday = 'Thursday' THEN activity_count END) AS \"Thur\", SUM(CASE WHEN weekday = 'Friday' THEN activity_count END) AS \"Fri\", SUM(CASE WHEN weekday = 'Saturday' THEN activity_count END) AS \"Sat\", SUM(CASE WHEN weekday = 'Sunday' THEN activity_count END) AS \"Sun\" FROM _final_dataset WHERE week_rank BETWEEN 1 AND 52 GROUP BY week_name) SELECT week_name, \"Mon\", \"Tue\", \"Wed\", \"Thur\", \"Fri\", \"Sat\", \"Sun\" FROM WeekSummary ORDER BY CAST(SUBSTRING(week_name FROM POSITION(' ' IN week_name) + 1 FOR 4) AS BIGINT) DESC NULLS LAST, CAST(SUBSTRING(week_name FROM 2 FOR POSITION(' ' IN week_name) - 2) AS BIGINT) DESC NULLS LAST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activity_count_per_day AS (SELECT \"Date\", COUNT(*) AS value FROM _activities GROUP BY 1), last_few_calendar_months AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS d, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'wIW YYYY') AS week_name, TO_CHAR(CAST(CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS TIMESTAMP), 'YYYYIW') AS week_number FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _calendar_months_with_rank AS (SELECT d, week_name || ' (' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' AS TIMESTAMP), 'MM/DD') || ' ~ ' || TO_CHAR(CAST(d - (EXTRACT(ISODOW FROM d) - 1) * INTERVAL '1 day' + INTERVAL '6 DAY' AS TIMESTAMP), 'MM/DD') || ')' AS week_name, \"week_number\", TO_CHAR(CAST(d AS TIMESTAMP), 'FMDay') AS weekday, DENSE_RANK() OVER (ORDER BY week_number DESC NULLS LAST) AS week_rank FROM last_few_calendar_months ORDER BY 1 DESC NULLS LAST), _final_dataset AS (SELECT _calendar_months_with_rank.*, CASE WHEN _activity_count_per_day.value IS NULL THEN 0 ELSE _activity_count_per_day.value END AS activity_count FROM _calendar_months_with_rank LEFT JOIN _activity_count_per_day ON _calendar_months_with_rank.d = _activity_count_per_day.\"Date\"), WeekSummary AS (SELECT week_name, SUM(CASE WHEN weekday = 'Monday' THEN activity_count END) AS \"Mon\", SUM(CASE WHEN weekday = 'Tuesday' THEN activity_count END) AS \"Tue\", SUM(CASE WHEN weekday = 'Wednesday' THEN activity_count END) AS \"Wed\", SUM(CASE WHEN weekday = 'Thursday' THEN activity_count END) AS \"Thur\", SUM(CASE WHEN weekday = 'Friday' THEN activity_count END) AS \"Fri\", SUM(CASE WHEN weekday = 'Saturday' THEN activity_count END) AS \"Sat\", SUM(CASE WHEN weekday = 'Sunday' THEN activity_count END) AS \"Sun\" FROM _final_dataset WHERE week_rank BETWEEN 1 AND 52 GROUP BY week_name) SELECT week_name, \"Mon\", \"Tue\", \"Wed\", \"Thur\", \"Fri\", \"Sat\", \"Sun\" FROM WeekSummary ORDER BY CAST(SUBSTRING(week_name FROM POSITION(' ' IN week_name) + 1 FOR 4) AS BIGINT) DESC NULLS LAST, CAST(SUBSTRING(week_name FROM 2 FOR POSITION(' ' IN week_name) - 2) AS BIGINT) DESC NULLS LAST", "refId": "A", "sql": { "columns": [ @@ -580,7 +580,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activities_per_day AS (SELECT \"Date\", SUM(CASE WHEN \"Activity\" = 'Create an issue' THEN 1 ELSE 0 END) AS \"Create an issue\", SUM(CASE WHEN \"Activity\" = 'Issue resolved' THEN 1 ELSE 0 END) AS \"Issue resolved\", SUM(CASE WHEN \"Activity\" = 'Finish a commit' THEN 1 ELSE 0 END) AS \"Finish a commit\", SUM(CASE WHEN \"Activity\" = 'Open a PR' THEN 1 ELSE 0 END) AS \"Open a PR\", SUM(CASE WHEN \"Activity\" = 'PR gets merged' THEN 1 ELSE 0 END) AS \"PR gets merged\", SUM(CASE WHEN \"Activity\" = 'Comment on PR' THEN 1 ELSE 0 END) AS \"Comment on PR\", SUM(CASE WHEN \"Activity\" = 'Review PR' THEN 1 ELSE 0 END) AS \"Review PR\" FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS \"Date\" FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY \"Date\" DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(CAST(_last_7_days.\"Date\" AS TIMESTAMP), 'DD/MM %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.\"Date\" = _activities_per_day.\"Date\" ORDER BY _last_7_days.\"Date\" ASC NULLS FIRST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _activities AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY \"Date\" ORDER BY \"Time\" DESC NULLS LAST) AS _row_number FROM (SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(resolution_date AS DATE) AS \"Date\", resolution_date AS \"Time\", 'Issue resolved' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.assignee_id = a.account_id WHERE $__timeFilter(resolution_date) UNION SELECT CAST(authored_date AS DATE) AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\" FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date) UNION SELECT CAST(created_date AS DATE) AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) UNION SELECT CAST(merged_date AS DATE) AS \"Date\", merged_date AS \"Time\", 'PR gets merged' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(merged_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type = 'NORMAL' AND $__timeFilter(prc.created_date) UNION SELECT CAST(prc.created_date AS DATE) AS \"Date\", prc.created_date AS \"Time\", 'Review PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\" FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE prc.type IN ('REVIEW', 'DIFF') AND $__timeFilter(prc.created_date)) AS t ORDER BY \"Time\" DESC NULLS LAST), _activities_per_day AS (SELECT \"Date\", SUM(CASE WHEN \"Activity\" = 'Create an issue' THEN 1 ELSE 0 END) AS \"Create an issue\", SUM(CASE WHEN \"Activity\" = 'Issue resolved' THEN 1 ELSE 0 END) AS \"Issue resolved\", SUM(CASE WHEN \"Activity\" = 'Finish a commit' THEN 1 ELSE 0 END) AS \"Finish a commit\", SUM(CASE WHEN \"Activity\" = 'Open a PR' THEN 1 ELSE 0 END) AS \"Open a PR\", SUM(CASE WHEN \"Activity\" = 'PR gets merged' THEN 1 ELSE 0 END) AS \"PR gets merged\", SUM(CASE WHEN \"Activity\" = 'Comment on PR' THEN 1 ELSE 0 END) AS \"Comment on PR\", SUM(CASE WHEN \"Activity\" = 'Review PR' THEN 1 ELSE 0 END) AS \"Review PR\" FROM _activities GROUP BY 1), _calendar_dates AS (/* construct the last few calendar months within the selected time period in the top-right corner */ SELECT CAST(($__timeTo()::timestamp - INTERVAL '1 day') AS DATE) AS \"Date\" FROM (SELECT 0 AS \"H\" UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300) AS \"H\" CROSS JOIN (SELECT 0 AS \"T\" UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30 UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60 UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90) AS \"T\" CROSS JOIN (SELECT 0 AS \"U\" UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS \"U\" WHERE ($__timeTo()::timestamp - INTERVAL '1 day') > $__timeFrom()), _last_7_days AS (SELECT * FROM _calendar_dates ORDER BY \"Date\" DESC NULLS LAST LIMIT 7) SELECT TO_CHAR(CAST(_last_7_days.\"Date\" AS TIMESTAMP), 'DD/MM %a') AS d, _activities_per_day.* FROM _last_7_days LEFT JOIN _activities_per_day ON _last_7_days.\"Date\" = _activities_per_day.\"Date\" ORDER BY _last_7_days.\"Date\" ASC NULLS FIRST", "refId": "A", "sql": { "columns": [ @@ -716,7 +716,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _issues AS (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", status, a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date)) SELECT status, COUNT(*) FROM _issues GROUP BY 1", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _issues AS (SELECT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Create an issue' AS \"Activity\", '#' || issue_key || ' ' || title AS \"Details\", status, a.name AS \"Name\" FROM issues AS i JOIN _accounts AS a ON i.creator_id = a.account_id WHERE $__timeFilter(created_date)) SELECT status, COUNT(*) FROM _issues GROUP BY 1", "refId": "A", "sql": { "columns": [ @@ -787,7 +787,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name, u.email FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)) SELECT COUNT(DISTINCT c.sha) FROM commits AS c JOIN _accounts AS a ON c.author_id = a.email WHERE $__timeFilter(authored_date)", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name, u.email FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))) SELECT COUNT(DISTINCT c.sha) FROM commits AS c JOIN _accounts AS a ON c.author_id = a.email WHERE $__timeFilter(authored_date)", "refId": "A", "sql": { "columns": [ @@ -859,7 +859,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)) SELECT SUM(additions + deletions) FROM _commits", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)) SELECT SUM(additions + deletions) FROM _commits", "refId": "A", "sql": { "columns": [ @@ -931,7 +931,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT pr.id AS pr_id, pr.merged_date, '#' || pr.pull_request_key || ' ' || pr.title AS details, prc.id AS comment_id, prc.created_date AS comment_date, a.name, prc.type AS comment_type FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id LEFT JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(pr.created_date)) SELECT COUNT(DISTINCT CASE WHEN NOT name IS NULL THEN pr_id END) || '/' || COUNT(DISTINCT pr_id) AS text FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _prs AS (SELECT pr.id AS pr_id, pr.merged_date, '#' || pr.pull_request_key || ' ' || pr.title AS details, prc.id AS comment_id, prc.created_date AS comment_date, a.name, prc.type AS comment_type FROM pull_requests AS pr LEFT JOIN pull_request_comments AS prc ON pr.id = prc.pull_request_id LEFT JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(pr.created_date)) SELECT COUNT(DISTINCT CASE WHEN NOT name IS NULL THEN pr_id END) || '/' || COUNT(DISTINCT pr_id) AS text FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1003,7 +1003,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT DISTINCT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\", pr.id, pr.merged_date FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date)) SELECT COUNT(CASE WHEN NOT merged_date IS NULL THEN id ELSE NULL END) || '/' || COUNT(DISTINCT id) FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _prs AS (SELECT DISTINCT TO_CHAR(CAST(created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", created_date AS \"Time\", 'Open a PR' AS \"Activity\", '#' || pull_request_key || ' ' || title AS \"Details\", a.name AS \"Name\", pr.id, pr.merged_date FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date)) SELECT COUNT(CASE WHEN NOT merged_date IS NULL THEN id ELSE NULL END) || '/' || COUNT(DISTINCT id) FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1121,7 +1121,7 @@ "hide": false, "metricColumn": "none", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_count FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) AND /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ NOT pr.merged_date IS NULL GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _prs AS (SELECT CAST(created_date AS DATE) + INTERVAL '1 DAY' * (-EXTRACT(DAY FROM CAST(created_date AS DATE)) + 1) AS time, COUNT(DISTINCT pr.id) AS pr_count FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(created_date) AND /* and created_date >= DATE_ADD(DATE_ADD($__timeFrom(), INTERVAL -EXTRACT(DAY FROM $__timeFrom())+1 DAY), INTERVAL +1 MONTH) */ NOT pr.merged_date IS NULL GROUP BY 1) SELECT TO_CHAR(CAST(time AS TIMESTAMP), 'FMMonth YYYY') AS month, pr_count AS \"Pull Request Count\" FROM _prs ORDER BY time NULLS FIRST", "refId": "A", "select": [ [ @@ -1226,7 +1226,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS \"PR Merged Size\" FROM _pr_commits_data", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _commits AS (SELECT DISTINCT TO_CHAR(CAST(authored_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", authored_date AS \"Time\", 'Finish a commit' AS \"Activity\", message || ' #' || sha AS \"Details\", a.name AS \"Name\", c.additions, c.deletions, c.sha FROM commits AS c JOIN _accounts AS a ON c.author_id = a.account_id WHERE $__timeFilter(authored_date)), _pr_commits_data AS (SELECT pr.id AS pr_id, pr.merge_commit_sha, SUM(c.additions) + SUM(c.deletions) AS loc FROM pull_requests AS pr LEFT JOIN _commits AS c ON pr.merge_commit_sha = c.sha WHERE $__timeFilter(pr.created_date) AND pr.status = 'MERGED' GROUP BY 1, 2) SELECT AVG(loc) AS \"PR Merged Size\" FROM _pr_commits_data", "refId": "A", "sql": { "columns": [ @@ -1298,7 +1298,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)), _prs AS (SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\", pr.id AS pr_id, prc.id AS prc_id FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(prc.created_date)) SELECT ROUND(CAST(CAST(COUNT(DISTINCT prc_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS DECIMAL), 1) FROM _prs", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))), _prs AS (SELECT TO_CHAR(CAST(prc.created_date AS TIMESTAMP), 'DD/MM/YYYY') AS \"Date\", prc.created_date AS \"Time\", 'Comment on PR' AS \"Activity\", '#' || pr.pull_request_key || ' ' || pr.title AS \"Details\", a.name AS \"Name\", pr.id AS pr_id, prc.id AS prc_id FROM pull_request_comments AS prc LEFT JOIN pull_requests AS pr ON prc.pull_request_id = pr.id JOIN _accounts AS a ON prc.account_id = a.account_id WHERE $__timeFilter(prc.created_date)) SELECT ROUND(CAST(CAST(COUNT(DISTINCT prc_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT pr_id), 0) AS DECIMAL), 1) FROM _prs", "refId": "A", "sql": { "columns": [ @@ -1371,7 +1371,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ua.user_id::text IN (SELECT v FROM unnest(CASE WHEN '$users' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$users]::text[] END) v)) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS \"Time to merge\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", + "rawSql": "WITH _accounts AS (SELECT ua.account_id, ua.user_id, u.name FROM accounts AS a JOIN user_accounts AS ua ON a.id = ua.account_id JOIN users AS u ON ua.user_id = u.id WHERE ('${users:csv}' = '' OR ua.user_id::text = ANY(ARRAY[${users:singlequote}]::text[]))) SELECT AVG(CAST((EXTRACT(EPOCH FROM (pr.merged_date - pr.created_date))/60) AS NUMERIC) / NULLIF(60, 0)) AS \"Time to merge\" FROM pull_requests AS pr JOIN _accounts AS a ON pr.author_id = a.account_id WHERE $__timeFilter(pr.created_date)", "refId": "A", "sql": { "columns": [ @@ -1437,14 +1437,14 @@ ] }, "datasource": "postgresql", - "definition": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE team_users.team_id::text IN (SELECT v FROM unnest(CASE WHEN '$team' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$team]::text[] END) v)", + "definition": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE ('${team:csv}' = '' OR team_users.team_id::text = ANY(ARRAY[${team:singlequote}]::text[]))", "hide": 0, "includeAll": true, "label": "User", "multi": true, "name": "users", "options": [], - "query": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE team_users.team_id::text IN (SELECT v FROM unnest(CASE WHEN '$team' = '' THEN ARRAY[NULL::text] ELSE ARRAY[$team]::text[] END) v)", + "query": "SELECT users.name || '--' || users.id FROM users LEFT JOIN team_users ON users.id = team_users.user_id WHERE ('${team:csv}' = '' OR team_users.team_id::text = ANY(ARRAY[${team:singlequote}]::text[]))", "refresh": 1, "regex": "/^(?.*)--(?.*)$/", "skipUrlSync": false, diff --git a/grafana/scripts/convert-mysql-to-postgresql.py b/grafana/scripts/convert-mysql-to-postgresql.py index 78624fe8390..3d70606a51b 100755 --- a/grafana/scripts/convert-mysql-to-postgresql.py +++ b/grafana/scripts/convert-mysql-to-postgresql.py @@ -196,6 +196,26 @@ def convert_single_quoted_alias(match): converted = re.sub(r'\bLIKE\s+"([^"]+)"', r"LIKE '\1'", converted, flags=re.IGNORECASE) converted = re.sub(r'\bNOT\s+LIKE\s+"([^"]+)"', r"NOT LIKE '\1'", converted, flags=re.IGNORECASE) + # Cast non-text columns to ::text for LIKE patterns + # PostgreSQL LIKE requires text type, MySQL allows implicit conversion + # Pattern: id LIKE '%value%' → id::text LIKE '%value%' + # id NOT LIKE '%x%' → id::text NOT LIKE '%x%' + # Single-pass pattern matches both LIKE and NOT LIKE + def add_text_cast_for_like_pattern(match): + column = match.group(1) + operator = match.group(2) # "LIKE" or "NOT LIKE" + # If column already has :: cast, don't add another + if '::' in column: + return match.group(0) + return f'{column}::text {operator} ' + + converted = re.sub( + r'\b([\w.]+(?:::\w+)?)\s+((?:NOT\s+)?LIKE)\s+', + add_text_cast_for_like_pattern, + converted, + flags=re.IGNORECASE + ) + # Convert double-quoted string literals to single quotes # Pattern: = "value", <> "value", != "value", IN ("val1", "val2"), CONCAT(..., "text", ...), THEN "value" # But skip: column aliases after AS (already handled above as AS "alias") @@ -355,30 +375,29 @@ def convert_substring_index(match): ) # Fix ambiguous identifier in GROUP BY when alias matches column name - # PostgreSQL folds unquoted identifiers to lowercase, causing ambiguity - # Quote all bare (unquoted, non-numeric) identifiers in GROUP BY + # PostgreSQL can't distinguish between alias and column with same name + # Keep GROUP BY as-is for now - dashboards using positional (1,2,3) work fine + # Dashboards using aliases may have ambiguity - fix at dashboard level def quote_group_by_identifiers(sql_text): def quote_group_by_item(match): prefix = match.group(1) # "GROUP BY " items_str = match.group(2) # rest of clause - # Split by comma to handle multiple items + # Keep positional numbers (1,2,3) as-is since they work perfectly + # Keep complex expressions (table.col) as-is + # Only quote simple identifiers for case-sensitivity items = [] for item in items_str.split(','): item = item.strip() - # Skip if already quoted, numeric positional, or empty - if not item or item.startswith('"') or item.isdigit(): + if not item or item.isdigit() or '.' in item: items.append(item) - # Check if it's a word (identifier) - quote it elif re.match(r'^\w+$', item): items.append(f'"{item}"') else: - # Complex expression (table.col, etc.) - keep as-is items.append(item) return prefix + ', '.join(items) - # Match GROUP BY clause (from GROUP BY to next keyword or end) sql_text = re.sub( r'\b(GROUP\s+BY\s+)([^;]*?)(?=\s*(?:HAVING|ORDER|LIMIT|$))', quote_group_by_item, @@ -508,26 +527,45 @@ def protect_equality_variable(match): converted ) - # PostgreSQL rejects IN () when Grafana variables are empty - wrap with CASE/unnest - # Also cast column to text to avoid type mismatch (bigint = text) + # PostgreSQL rejects IN () when Grafana variables are empty + # Use = ANY(ARRAY[...]) instead of IN (...) because empty array is valid SQL + # When empty: = ANY(ARRAY[]::text[]) is always false + # When multi-value: = ANY(ARRAY['val1','val2']::text[]) filters correctly + def format_grafana_variable(var): + """Extract variable name and return (var_formatted, var_check) for Grafana expansion.""" + if var.startswith('${') and var.endswith('}'): + var_name = var[2:-1] + if ':' not in var_name: + return f'${{{var_name}:singlequote}}', f'${{{var_name}:csv}}' + return var, var + # Bare $var form + var_name = var[1:] + return f'${{{var_name}:singlequote}}', f'${{{var_name}:csv}}' + def protect_in_clause_with_cast(match): column = match.group(1) var = match.group(2) - # Cast column to text if not already cast if '::' not in column: column = f'{column}::text' - return f"{column} IN (SELECT v FROM unnest(CASE WHEN '{var}' = '' THEN ARRAY[NULL::text] ELSE ARRAY[{var}]::text[] END) v)" + var_formatted, var_check = format_grafana_variable(var) + return f"('{var_check}' = '' OR {column} = ANY(ARRAY[{var_formatted}]::text[]))" + + def protect_function_in_clause(match): + func_expr = match.group(1) + var = match.group(2) + var_formatted, var_check = format_grafana_variable(var) + return f"('{var_check}' = '' OR {func_expr} = ANY(ARRAY[{var_formatted}]::text[]))" converted = re.sub( - r'(\S+)\s+IN\s*\(\s*(\$\{[^}]+\})\s*\)', # column IN (${var}) + r'([\w.]+(?:::\w+)?)\s+IN\s*\(\s*(\$\{[^}]+\}|\$(?!__)[a-z_][a-z0-9_]*)\s*\)', protect_in_clause_with_cast, converted, flags=re.IGNORECASE ) converted = re.sub( - r'(\S+)\s+IN\s*\(\s*(\$(?!__)[a-z_][a-z0-9_]*)\s*\)', # column IN ($var) - protect_in_clause_with_cast, + r'(SPLIT_PART\([^)]+\))\s+IN\s*\(\s*(\$\{[^}]+\}|\$(?!__)[a-z_][a-z0-9_]*)\s*\)', + protect_function_in_clause, converted, flags=re.IGNORECASE ) diff --git a/grafana/scripts/entrypoint.sh b/grafana/scripts/entrypoint.sh index 10e83f283c7..7dc483e270b 100644 --- a/grafana/scripts/entrypoint.sh +++ b/grafana/scripts/entrypoint.sh @@ -62,11 +62,17 @@ echo "Database type: $MODE" # Remove unused dashboard folder to prevent confusion if [ "$MODE" = "mysql" ]; then rm -rf /etc/grafana/dashboards/postgresql + export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/mysql/Homepage.json" else rm -rf /etc/grafana/dashboards/mysql + SSL_MODE="${DATABASE_SSL_MODE:-disable}" + export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/postgresql/Homepage.json" + echo "SSL Mode: ${SSL_MODE}" fi -# Generate datasource.yml +echo "Homepage: $GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH" + +# Create empty datasource.yml (datasources created via API) cat > "$DATASOURCE_FILE" << HEADER # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -82,41 +88,91 @@ cat > "$DATASOURCE_FILE" << HEADER # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +HEADER -apiVersion: 1 +# Start Grafana in background +/run.sh "$@" & +GRAFANA_PID=$! -datasources: -HEADER +# Wait for Grafana API +echo "Waiting for Grafana API..." +for i in $(seq 1 60); do + if curl -f -s http://localhost:3000/api/health >/dev/null 2>&1; then + echo "Grafana API ready" + sleep 5 # Extra wait for migrations + break + fi + sleep 2 +done + +# Create datasource via API (both MySQL and PostgreSQL) +PAYLOAD_FILE="/tmp/datasource-api.json" if [ "$MODE" = "mysql" ]; then - cat >> "$DATASOURCE_FILE" << MYSQL_DS - - name: mysql - type: mysql - url: ${MYSQL_URL} - database: ${MYSQL_DATABASE} - user: ${MYSQL_USER} - secureJsonData: - password: ${MYSQL_PASSWORD} - editable: false -MYSQL_DS - export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/mysql/Homepage.json" + cat > "$PAYLOAD_FILE" <&1 || true + curl -s -X DELETE "http://admin:${GF_SECURITY_ADMIN_PASSWORD}@localhost:3000/api/datasources/uid/devlake-mysql-api" 2>&1 || true + sleep 2 + else - cat >> "$DATASOURCE_FILE" << POSTGRES_DS - - name: postgresql - type: postgres - url: ${POSTGRES_URL} - database: ${POSTGRES_DATABASE} - user: ${POSTGRES_USER} - secureJsonData: - password: ${POSTGRES_PASSWORD} - editable: false - jsonData: - sslmode: disable - postgresVersion: 1400 -POSTGRES_DS - export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/postgresql/Homepage.json" + SSL_MODE="${DATABASE_SSL_MODE:-disable}" + cat > "$PAYLOAD_FILE" <&1) + + if echo "$RESPONSE" | grep -q '"id"'; then + echo "Datasource created successfully" + break + elif echo "$RESPONSE" | grep -q "database is locked"; then + echo "DB locked, retry $i/10..." + sleep 3 + else + echo "API error: $RESPONSE" + sleep 2 + fi +done -exec /run.sh "$@" +# Wait for Grafana +wait $GRAFANA_PID