From c830eae1101f8c807d299aec3036210794a635d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 16:11:02 +0000 Subject: [PATCH 01/22] Bump pymysql from 1.1.0 to 1.1.1 in /engine (#4372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [pymysql](https://github.com/PyMySQL/PyMySQL) from 1.1.0 to 1.1.1.
Release notes

Sourced from pymysql's releases.

v1.1.1

[!WARNING] This release fixes a vulnerability (CVE-2024-36039). All users are recommended to update to this version.

If you can not update soon, check the input value from untrusted source has an expected type. Only dict input from untrusted source can be an attack vector.

What's Changed

Merged PRs

New Contributors

Full Changelog: https://github.com/PyMySQL/PyMySQL/compare/v1.1.0...v1.1.1

Changelog

Sourced from pymysql's changelog.

v1.1.1

Release date: 2024-05-21

[!WARNING] This release fixes a vulnerability (CVE-2024-36039). All users are recommended to update to this version.

If you can not update soon, check the input value from untrusted source has an expected type. Only dict input from untrusted source can be an attack vector.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pymysql&package-manager=pip&previous-version=1.1.0&new-version=1.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/grafana/oncall/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Joey Orlando --- engine/requirements.in | 2 +- engine/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/requirements.in b/engine/requirements.in index af198397..4426371b 100644 --- a/engine/requirements.in +++ b/engine/requirements.in @@ -48,7 +48,7 @@ prometheus_client==0.16.0 psutil==5.9.4 psycopg2==2.9.3 pymdown-extensions==10.0 -PyMySQL==1.1.0 +PyMySQL==1.1.1 python-telegram-bot==13.13 recurring-ical-events==2.1.0 redis==5.0.1 diff --git a/engine/requirements.txt b/engine/requirements.txt index 8c910230..a096e2f2 100644 --- a/engine/requirements.txt +++ b/engine/requirements.txt @@ -349,7 +349,7 @@ pyjwt==2.8.0 # twilio pymdown-extensions==10.0 # via -r requirements.in -pymysql==1.1.0 +pymysql==1.1.1 # via -r requirements.in pyopenssl==23.2.0 # via django-sns-view From f3b2d8e92739b2e4967d89fe0adedece9abbddc8 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Wed, 22 May 2024 12:31:18 -0600 Subject: [PATCH 02/22] Add details to escalation step description (#4334) Attempt to clarify how `Notify whole slack channel` and `Notify Slack User Group` work --- .../sources/configure/escalation-chains-and-routes/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sources/configure/escalation-chains-and-routes/index.md b/docs/sources/configure/escalation-chains-and-routes/index.md index da799119..8db2359f 100644 --- a/docs/sources/configure/escalation-chains-and-routes/index.md +++ b/docs/sources/configure/escalation-chains-and-routes/index.md @@ -83,8 +83,10 @@ from an on-call schedule. * `Notify all users from a team` - send a notification to all users in a team. * `Resolve incident automatically` - resolve the alert group right now with status `Resolved automatically`. -* `Notify whole slack channel` - send a notification to the users in the slack channel. -* `Notify Slack User Group` - send a notification to each member of a slack user group. +* `Notify whole slack channel` - send a notification to the users in the slack channel. These users will be notified +via the method configured in their user profile. +* `Notify Slack User Group` - send a notification to each member of a slack user group. These users will be notified +via the method configured in their user profile. * `Trigger outgoing webhook` - trigger an [outgoing webhook]. * `Notify users one by one (round robin)` - each notification will be sent to a group of users one by one, in sequential order in [round robin fashion](https://en.wikipedia.org/wiki/Round-robin_item_allocation). From 375f6baf50bac51dc7e7714ff13e08c14b473a17 Mon Sep 17 00:00:00 2001 From: Dominik Broj Date: Thu, 23 May 2024 05:45:25 +0200 Subject: [PATCH 03/22] Add service_name to Insights filters (#4133) # What this PR does ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall-private/issues/2610 Milestone: https://github.com/grafana/oncall-private/issues/2552 ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- .../dashboards/oncall_metrics_dashboard.json | 49 +++++++++++++++---- .../scenes/AlertGroupsByIntegration.tsx | 2 +- .../insights/scenes/AlertGroupsByTeam.tsx | 2 +- .../pages/insights/scenes/MTTRAverageStat.tsx | 2 +- .../insights/scenes/MTTRByIntegration.tsx | 2 +- .../src/pages/insights/scenes/MTTRByTeam.tsx | 2 +- .../insights/scenes/MTTRChangedTimeseries.tsx | 2 +- .../pages/insights/scenes/NewAlertGroups.tsx | 2 +- .../NewAlertGroupsNotificationsTable.tsx | 2 +- .../NewAlertGroupsNotificationsTimeseries.tsx | 2 +- .../scenes/NewAlertGroupsTimeseries.tsx | 2 +- .../src/pages/insights/variables.ts | 15 ++++++ 12 files changed, 64 insertions(+), 20 deletions(-) diff --git a/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json b/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json index 68e0d32f..791aa6f2 100644 --- a/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json +++ b/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json @@ -182,7 +182,7 @@ "editorMode": "code", "excludeNullMetadata": false, "exemplar": false, - "expr": "sum(round(delta($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}[$__range]))) >= 0", + "expr": "round(delta(sum($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"})[$__range:])) >= 0", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -266,7 +266,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}) / sum($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}))[$__range:])", + "expr": "avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}) / sum($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}))[$__range:])", "instant": true, "legendFormat": "__auto", "range": false, @@ -389,7 +389,7 @@ "editorMode": "code", "excludeNullMetadata": false, "exemplar": false, - "expr": "sum by (integration)(round(delta($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}[$__interval:]))) >= 0", + "expr": "round(delta(sum by (integration) ($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"})[$__interval:])) >= 0", "fullMetaSearch": false, "hide": false, "instant": false, @@ -507,7 +507,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg(sum($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}) / sum($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}))", + "expr": "avg(sum($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}) / sum($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}))", "instant": false, "legendFormat": "__auto", "range": true, @@ -613,7 +613,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(sum by (integration)(round(delta($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}[$__range]))) >= 0)", + "expr": "sort_desc(round(delta(sum by (integration)($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"})[$__range:])) >= 0)", "format": "table", "instant": true, "legendFormat": "__auto", @@ -729,7 +729,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}))[$__range:]))", + "expr": "sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}))[$__range:]))", "format": "table", "instant": true, "legendFormat": "__auto", @@ -862,7 +862,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(sum by (team)(round(delta($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}[$__range]))) >= 0)", + "expr": "sort_desc(round(delta(sum by (team)($alert_groups_total{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"})[$__range:])) >= 0)", "format": "table", "instant": true, "legendFormat": "__auto", @@ -979,7 +979,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\"}))[$__range:]))", + "expr": "sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~\"$instance\", team=~\"$team\", integration=~\"$integration\", service_name=~\"$service_name\"}))[$__range:]))", "format": "table", "instant": true, "legendFormat": "__auto", @@ -1129,7 +1129,7 @@ "editorMode": "code", "excludeNullMetadata": false, "exemplar": false, - "expr": "sum by (username)(round(delta($user_was_notified_of_alert_groups_total{slug=~\"$instance\"}[$__interval:]))) >= 0", + "expr": "round(delta(sum by (username)($user_was_notified_of_alert_groups_total{slug=~\"$instance\"})[$__interval:])) >= 0", "fullMetaSearch": false, "instant": false, "legendFormat": "__auto", @@ -1222,7 +1222,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(sum by (username)(round(delta($user_was_notified_of_alert_groups_total{slug=~\"$instance\"}[$__range]))) >= 0)", + "expr": "sort_desc(round(delta(sum by (username)($user_was_notified_of_alert_groups_total{slug=~\"$instance\"})[$__range:])) >= 0)", "format": "table", "instant": true, "legendFormat": "__auto", @@ -1471,6 +1471,35 @@ "skipUrlSync": false, "sort": 0, "type": "query" + }, + { + "allValue": "($^)|(.+)", + "current": { + "selected": true, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(${alert_groups_total}{slug=~\"$instance\", team=~\"$team\"},service_name)", + "hide": 0, + "includeAll": true, + "label": "Service name", + "multi": true, + "name": "service_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(${alert_groups_total}{slug=~\"$instance\", team=~\"$team\"},service_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" } ] }, diff --git a/grafana-plugin/src/pages/insights/scenes/AlertGroupsByIntegration.tsx b/grafana-plugin/src/pages/insights/scenes/AlertGroupsByIntegration.tsx index 4a0efeff..fdc194d5 100644 --- a/grafana-plugin/src/pages/insights/scenes/AlertGroupsByIntegration.tsx +++ b/grafana-plugin/src/pages/insights/scenes/AlertGroupsByIntegration.tsx @@ -10,7 +10,7 @@ export function getAlertGroupsByIntegrationScene({ datasource, stack }: Insights { editorMode: 'code', exemplar: false, - expr: `sort_desc(sum by (integration)(round(delta($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}[$__range]))) >= 0)`, + expr: `sort_desc(round(delta(sum by (integration)($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"})[$__range:])) >= 0)`, format: 'table', instant: true, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/AlertGroupsByTeam.tsx b/grafana-plugin/src/pages/insights/scenes/AlertGroupsByTeam.tsx index 15c7f27f..43352671 100644 --- a/grafana-plugin/src/pages/insights/scenes/AlertGroupsByTeam.tsx +++ b/grafana-plugin/src/pages/insights/scenes/AlertGroupsByTeam.tsx @@ -10,7 +10,7 @@ export function getAlertGroupsByTeamScene({ datasource, stack }: InsightsConfig) { editorMode: 'code', exemplar: false, - expr: `sort_desc(sum by (team)(round(delta($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}[$__range]))) >= 0)`, + expr: `sort_desc(round(delta(sum by (team)($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"})[$__range:])) >= 0)`, format: 'table', instant: true, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/MTTRAverageStat.tsx b/grafana-plugin/src/pages/insights/scenes/MTTRAverageStat.tsx index 9e394668..5f61359b 100644 --- a/grafana-plugin/src/pages/insights/scenes/MTTRAverageStat.tsx +++ b/grafana-plugin/src/pages/insights/scenes/MTTRAverageStat.tsx @@ -10,7 +10,7 @@ export function getMTTRAverage({ datasource, stack }: InsightsConfig) { { editorMode: 'code', exemplar: false, - expr: `avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:])`, + expr: `avg_over_time((sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}))[$__range:])`, instant: true, legendFormat: '__auto', range: false, diff --git a/grafana-plugin/src/pages/insights/scenes/MTTRByIntegration.tsx b/grafana-plugin/src/pages/insights/scenes/MTTRByIntegration.tsx index 63bd86d9..aab2c746 100644 --- a/grafana-plugin/src/pages/insights/scenes/MTTRByIntegration.tsx +++ b/grafana-plugin/src/pages/insights/scenes/MTTRByIntegration.tsx @@ -10,7 +10,7 @@ export function getMTTRByIntegrationScene({ datasource, stack }: InsightsConfig) { editorMode: 'code', exemplar: false, - expr: `sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:]))`, + expr: `sort_desc(avg_over_time((sum by (integration)($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}) / sum by (integration)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}))[$__range:]))`, format: 'table', instant: true, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/MTTRByTeam.tsx b/grafana-plugin/src/pages/insights/scenes/MTTRByTeam.tsx index 8ca49c60..d86fcedb 100644 --- a/grafana-plugin/src/pages/insights/scenes/MTTRByTeam.tsx +++ b/grafana-plugin/src/pages/insights/scenes/MTTRByTeam.tsx @@ -10,7 +10,7 @@ export function getMTTRByTeamScene({ datasource, stack }: InsightsConfig) { { editorMode: 'code', exemplar: false, - expr: `sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))[$__range:]))`, + expr: `sort_desc(avg_over_time((sum by(team) ($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}) / sum by(team)($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}))[$__range:]))`, format: 'table', instant: true, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/MTTRChangedTimeseries.tsx b/grafana-plugin/src/pages/insights/scenes/MTTRChangedTimeseries.tsx index 36003b62..1614bbb6 100644 --- a/grafana-plugin/src/pages/insights/scenes/MTTRChangedTimeseries.tsx +++ b/grafana-plugin/src/pages/insights/scenes/MTTRChangedTimeseries.tsx @@ -10,7 +10,7 @@ export function getMTTRChangedTimeseriesScene({ datasource, stack }: InsightsCon { editorMode: 'code', exemplar: false, - expr: `avg(sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration"}))`, + expr: `avg(sum($alert_groups_response_time_seconds_sum{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}) / sum($alert_groups_response_time_seconds_count{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"}))`, instant: false, legendFormat: '__auto', range: true, diff --git a/grafana-plugin/src/pages/insights/scenes/NewAlertGroups.tsx b/grafana-plugin/src/pages/insights/scenes/NewAlertGroups.tsx index b50de84c..ab187c24 100644 --- a/grafana-plugin/src/pages/insights/scenes/NewAlertGroups.tsx +++ b/grafana-plugin/src/pages/insights/scenes/NewAlertGroups.tsx @@ -12,7 +12,7 @@ export function getNewAlertGroupsScene({ datasource, stack }: InsightsConfig) { editorMode: 'code', excludeNullMetadata: false, exemplar: false, - expr: `sum(round(delta($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}[$__range]))) >= 0`, + expr: `round(delta(sum($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"})[$__range:])) >= 0`, format: 'time_series', fullMetaSearch: false, includeNullMetadata: true, diff --git a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTable.tsx b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTable.tsx index c04e18e7..f6d9e7ba 100644 --- a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTable.tsx +++ b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTable.tsx @@ -10,7 +10,7 @@ export function getNewAlertGroupsNotificationsTableScene({ datasource, stack }: { editorMode: 'code', exemplar: false, - expr: `sort_desc(sum by (username)(round(delta($user_was_notified_of_alert_groups_total{slug=~"${stack}"}[$__range]))) >= 0)`, + expr: `sort_desc(round(delta(sum by (username)($user_was_notified_of_alert_groups_total{slug=~"${stack}"})[$__range:])) >= 0)`, format: 'table', instant: true, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTimeseries.tsx b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTimeseries.tsx index 297d57a0..bc2ae156 100644 --- a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTimeseries.tsx +++ b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsNotificationsTimeseries.tsx @@ -13,7 +13,7 @@ export function getNewAlertGroupsNotificationsTimeseriesScene({ datasource, stac editorMode: 'code', excludeNullMetadata: false, exemplar: false, - expr: `sum by (username)(round(delta($user_was_notified_of_alert_groups_total{slug=~"${stack}"}[$__interval:]))) >= 0`, + expr: `round(delta(sum by (username)($user_was_notified_of_alert_groups_total{slug=~"${stack}"})[$__interval:])) >= 0`, fullMetaSearch: false, instant: false, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsTimeseries.tsx b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsTimeseries.tsx index 3dd375be..cd556708 100644 --- a/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsTimeseries.tsx +++ b/grafana-plugin/src/pages/insights/scenes/NewAlertGroupsTimeseries.tsx @@ -13,7 +13,7 @@ export function getNewAlertGroupsTimeseriesScene({ datasource, stack }: Insights editorMode: 'code', excludeNullMetadata: false, exemplar: false, - expr: `sum by (integration)(round(delta($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration"}[$__interval:]))) >= 0`, + expr: `round(delta(sum by (integration)($alert_groups_total{slug=~"${stack}", team=~"$team", integration=~"$integration", service_name=~"$service_name"})[$__interval:])) >= 0`, fullMetaSearch: false, instant: false, legendFormat: '__auto', diff --git a/grafana-plugin/src/pages/insights/variables.ts b/grafana-plugin/src/pages/insights/variables.ts index a1ba585d..574bbebc 100644 --- a/grafana-plugin/src/pages/insights/variables.ts +++ b/grafana-plugin/src/pages/insights/variables.ts @@ -54,6 +54,21 @@ const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({ }, refresh: 2, }), + service_name: new QueryVariable({ + ...DEFAULT_VARIABLE_CONFIG, + name: 'service_name', + label: 'Service name', + text: ['All'], + value: ['$__all'], + allValue: '($^)|(.+)', + datasource, + definition: `label_values(\${alert_groups_total}{slug=~"${stack}",team=~"$team"},service_name)`, + query: { + query: `label_values(\${alert_groups_total}{slug=~"${stack}",team=~"$team"},service_name)`, + refId: 'PrometheusVariableQueryEditor-VariableQuery', + }, + refresh: 2, + }), // Non-selectable alertGroupsTotal: new QueryVariable({ From 764606089d20d505a38ccdf91f2180c33c711438 Mon Sep 17 00:00:00 2001 From: Dominik Broj Date: Thu, 23 May 2024 07:53:11 +0200 Subject: [PATCH 04/22] show datasource dropdown on cloud (#4382) # What this PR does - show data source filter also on cloud - use custom All option value to prevent invalid queries when there are too many values ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- .../dashboards/oncall_metrics_dashboard.json | 3 +++ .../src/pages/insights/variables.ts | 20 ++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json b/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json index 791aa6f2..aa424065 100644 --- a/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json +++ b/grafana-plugin/src/dashboards/oncall_metrics_dashboard.json @@ -1392,6 +1392,7 @@ "type": "query" }, { + "allValue": ".+", "current": { "selected": true, "text": ["All"], @@ -1419,6 +1420,7 @@ "type": "query" }, { + "allValue": ".+", "current": { "selected": true, "text": ["All"], @@ -1446,6 +1448,7 @@ "type": "query" }, { + "allValue": ".+", "current": { "selected": true, "text": ["All"], diff --git a/grafana-plugin/src/pages/insights/variables.ts b/grafana-plugin/src/pages/insights/variables.ts index 574bbebc..199bcfd9 100644 --- a/grafana-plugin/src/pages/insights/variables.ts +++ b/grafana-plugin/src/pages/insights/variables.ts @@ -5,6 +5,7 @@ import { InsightsConfig } from './Insights.types'; const DEFAULT_VARIABLE_CONFIG: Partial[0]> = { hide: 0, includeAll: true, + allValue: `.+`, isMulti: true, options: [], refresh: 1, @@ -14,18 +15,13 @@ const DEFAULT_VARIABLE_CONFIG: Partial ({ - // Selectable - ...(isOpenSource - ? { - datasource: new DataSourceVariable({ - name: 'datasource', - label: 'Data source', - pluginId: 'prometheus', - value: 'grafanacloud-usage', - }), - } - : {}), +const getVariables = ({ datasource, stack }: InsightsConfig) => ({ + datasource: new DataSourceVariable({ + name: 'datasource', + label: 'Data source', + pluginId: 'prometheus', + value: 'grafanacloud-usage', + }), team: new QueryVariable({ ...DEFAULT_VARIABLE_CONFIG, name: 'team', From f9f5ae9042b85eca7fea6de8d085147ae3ab57bf Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 23 May 2024 12:06:00 +0100 Subject: [PATCH 05/22] fix silence_options schema (#4384) --- engine/apps/api/views/alert_group.py | 1 + .../src/network/oncall-api/autogenerated-api.types.d.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/engine/apps/api/views/alert_group.py b/engine/apps/api/views/alert_group.py index 1c6ce144..57a10ecf 100644 --- a/engine/apps/api/views/alert_group.py +++ b/engine/apps/api/views/alert_group.py @@ -638,6 +638,7 @@ class AlertGroupView( choices=[display_name for _, display_name in AlertGroup.SILENCE_DELAY_OPTIONS] ), }, + many=True, ) ) @action(methods=["get"], detail=False) diff --git a/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts b/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts index ae7e4aab..8ac1cdef 100644 --- a/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts +++ b/grafana-plugin/src/network/oncall-api/autogenerated-api.types.d.ts @@ -823,7 +823,7 @@ export interface paths { cookie?: never; }; /** @description Retrieve a list of valid silence options */ - get: operations['alertgroups_silence_options_retrieve']; + get: operations['alertgroups_silence_options_list']; put?: never; post?: never; delete?: never; @@ -3765,7 +3765,7 @@ export interface operations { }; }; }; - alertgroups_silence_options_retrieve: { + alertgroups_silence_options_list: { parameters: { query?: never; header?: never; @@ -3779,7 +3779,7 @@ export interface operations { [name: string]: unknown; }; content: { - 'application/json': components['schemas']['AlertGroupSilenceOptions']; + 'application/json': components['schemas']['AlertGroupSilenceOptions'][]; }; }; }; From d316c9121e0c81040d9f43e63c4759c1fe31f16e Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Thu, 23 May 2024 09:36:28 -0300 Subject: [PATCH 06/22] Fix order filtering when executing notify all/group steps from snapshot (#4381) Fixes https://github.com/grafana/oncall-private/issues/2708 --- engine/apps/alerts/tasks/notify_all.py | 8 +- engine/apps/alerts/tasks/notify_group.py | 8 +- engine/apps/alerts/tests/test_notify_all.py | 78 +++++++++++++++++ engine/apps/alerts/tests/test_notify_group.py | 84 +++++++++++++++++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 engine/apps/alerts/tests/test_notify_all.py create mode 100644 engine/apps/alerts/tests/test_notify_group.py diff --git a/engine/apps/alerts/tasks/notify_all.py b/engine/apps/alerts/tasks/notify_all.py index 0b29837c..30b7c1d4 100644 --- a/engine/apps/alerts/tasks/notify_all.py +++ b/engine/apps/alerts/tasks/notify_all.py @@ -22,7 +22,13 @@ def notify_all_task(alert_group_pk, escalation_policy_snapshot_order=None): escalation_snapshot = alert_group.escalation_snapshot try: - escalation_policy_snapshot = escalation_snapshot.escalation_policies_snapshots[escalation_policy_snapshot_order] + # escalation_policy_snapshot_order refers to order as defined in the policy, + # which is unique but not necessarily sequential and may not start from zero + escalation_policy_snapshot = [ + policy + for policy in escalation_snapshot.escalation_policies_snapshots + if policy.order == escalation_policy_snapshot_order + ][0] except IndexError: escalation_policy_snapshot = None diff --git a/engine/apps/alerts/tasks/notify_group.py b/engine/apps/alerts/tasks/notify_group.py index efaee18d..0d95e528 100644 --- a/engine/apps/alerts/tasks/notify_group.py +++ b/engine/apps/alerts/tasks/notify_group.py @@ -34,7 +34,13 @@ def notify_group_task(alert_group_pk, escalation_policy_snapshot_order=None): escalation_snapshot = alert_group.escalation_snapshot try: - escalation_policy_snapshot = escalation_snapshot.escalation_policies_snapshots[escalation_policy_snapshot_order] + # escalation_policy_snapshot_order refers to order as defined in the policy, + # which is unique but not necessarily sequential and may not start from zero + escalation_policy_snapshot = [ + policy + for policy in escalation_snapshot.escalation_policies_snapshots + if policy.order == escalation_policy_snapshot_order + ][0] except IndexError: escalation_policy_snapshot = None diff --git a/engine/apps/alerts/tests/test_notify_all.py b/engine/apps/alerts/tests/test_notify_all.py new file mode 100644 index 00000000..a513472f --- /dev/null +++ b/engine/apps/alerts/tests/test_notify_all.py @@ -0,0 +1,78 @@ +from unittest.mock import patch + +import pytest + +from apps.alerts.models import EscalationPolicy +from apps.alerts.tasks.notify_all import notify_all_task +from apps.base.models.user_notification_policy import UserNotificationPolicy + + +@pytest.mark.django_db +def test_notify_all( + make_organization, + make_slack_team_identity, + make_user, + make_user_notification_policy, + make_escalation_chain, + make_escalation_policy, + make_channel_filter, + make_alert_receive_channel, + make_alert_group, +): + organization = make_organization() + slack_team_identity = make_slack_team_identity() + organization.slack_team_identity = slack_team_identity + organization.save() + + user = make_user(organization=organization) + make_user_notification_policy( + user=user, + step=UserNotificationPolicy.Step.NOTIFY, + notify_by=UserNotificationPolicy.NotificationChannel.SMS, + ) + alert_receive_channel = make_alert_receive_channel(organization=organization) + + escalation_chain = make_escalation_chain(organization) + channel_filter = make_channel_filter( + alert_receive_channel, + escalation_chain=escalation_chain, + notify_in_slack=True, + slack_channel_id="slack-channel-id", + ) + # note this is the only escalation step, with order=1 + notify_all = make_escalation_policy( + order=1, + escalation_chain=channel_filter.escalation_chain, + escalation_policy_step=EscalationPolicy.STEP_FINAL_NOTIFYALL, + ) + alert_group = make_alert_group(alert_receive_channel=alert_receive_channel, channel_filter=channel_filter) + # build escalation snapshot + alert_group.raw_escalation_snapshot = alert_group.build_raw_escalation_snapshot() + alert_group.save() + + with patch( + "apps.slack.models.SlackTeamIdentity.get_users_from_slack_conversation_for_organization" + ) as mock_get_users: + mock_get_users.return_value = [user] + with patch("apps.alerts.tasks.notify_all.notify_user_task") as mock_notify_user_task: + notify_all_task(alert_group.pk, escalation_policy_snapshot_order=1) + + alert_group.refresh_from_db() + + # check triggered log + log_record = alert_group.log_records.last() + assert log_record.type == log_record.TYPE_ESCALATION_TRIGGERED + assert log_record.author == user + assert log_record.escalation_policy == notify_all + assert log_record.escalation_policy_step == EscalationPolicy.STEP_FINAL_NOTIFYALL + + # check user is notified + mock_notify_user_task.apply_async.assert_called_once_with( + args=(user.pk, alert_group.pk), + kwargs={"reason": "notifying everyone in the channel", "prevent_posting_to_thread": True}, + countdown=0, + ) + + escalation_snapshot = alert_group.escalation_snapshot + assert escalation_snapshot is not None + assert escalation_snapshot.escalation_policies_snapshots[0].notify_to_users_queue == [user] diff --git a/engine/apps/alerts/tests/test_notify_group.py b/engine/apps/alerts/tests/test_notify_group.py new file mode 100644 index 00000000..e03acebb --- /dev/null +++ b/engine/apps/alerts/tests/test_notify_group.py @@ -0,0 +1,84 @@ +from unittest.mock import patch + +import pytest + +from apps.alerts.models import EscalationPolicy +from apps.alerts.tasks.notify_group import notify_group_task +from apps.base.models.user_notification_policy import UserNotificationPolicy + + +@pytest.mark.django_db +def test_notify_group( + make_organization, + make_slack_team_identity, + make_user, + make_user_notification_policy, + make_escalation_chain, + make_escalation_policy, + make_channel_filter, + make_slack_user_group, + make_alert_receive_channel, + make_alert_group, +): + organization = make_organization() + slack_team_identity = make_slack_team_identity() + organization.slack_team_identity = slack_team_identity + organization.save() + + user = make_user(organization=organization) + # remove default email escalation policies + user.notification_policies.all().delete() + make_user_notification_policy( + user=user, + step=UserNotificationPolicy.Step.NOTIFY, + notify_by=UserNotificationPolicy.NotificationChannel.SMS, + ) + alert_receive_channel = make_alert_receive_channel(organization=organization) + + escalation_chain = make_escalation_chain(organization) + channel_filter = make_channel_filter( + alert_receive_channel, + escalation_chain=escalation_chain, + notify_in_slack=True, + slack_channel_id="slack-channel-id", + ) + usergroup = make_slack_user_group(slack_team_identity) + # note this is the only escalation step, with order=1 + notify_group = make_escalation_policy( + order=1, + escalation_chain=channel_filter.escalation_chain, + escalation_policy_step=EscalationPolicy.STEP_NOTIFY_GROUP, + notify_to_group=usergroup, + ) + alert_group = make_alert_group(alert_receive_channel=alert_receive_channel, channel_filter=channel_filter) + # build escalation snapshot + alert_group.raw_escalation_snapshot = alert_group.build_raw_escalation_snapshot() + alert_group.save() + + with patch("apps.slack.models.SlackUserGroup.get_users_from_members_for_organization") as mock_get_users: + mock_get_users.return_value = [user] + with patch("apps.alerts.tasks.notify_group.notify_user_task") as mock_notify_user_task: + notify_group_task(alert_group.pk, escalation_policy_snapshot_order=1) + + alert_group.refresh_from_db() + + # check triggered log + log_record = alert_group.log_records.last() + assert log_record.type == log_record.TYPE_ESCALATION_TRIGGERED + assert log_record.escalation_policy == notify_group + assert log_record.escalation_policy_step == EscalationPolicy.STEP_NOTIFY_GROUP + assert log_record.step_specific_info == {"usergroup_handle": usergroup.handle} + + # check user is notified + mock_notify_user_task.apply_async.assert_called_once_with( + args=(user.pk, alert_group.pk), + kwargs={ + "reason": f"Membership in User Group", + "prevent_posting_to_thread": True, + "important": False, + }, + ) + + escalation_snapshot = alert_group.escalation_snapshot + assert escalation_snapshot is not None + assert escalation_snapshot.escalation_policies_snapshots[0].notify_to_users_queue == [user] From a1207eb0a4fe58d90fea257f026ed00b80ee33d4 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Thu, 23 May 2024 10:35:13 -0600 Subject: [PATCH 07/22] Fix missing filtering term type 1 when editing jinja route template (#4357) # What this PR does When editing a jinja route template the filtering_term_type was not being provided so it was being validated as regex instead. This adds the missing flag so that the route will use the correct validation. The reason this was not being hit in the past is most jinja templates were still passing regex validation. ## Which issue(s) this PR closes Closes #4259 ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- grafana-plugin/src/pages/integration/Integration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index 64c9c587..6f2ec9b6 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -186,7 +186,7 @@ class _IntegrationPage extends React.Component this.onUpdateRoutesCallback(values, channelFilterId, 1)} template={selectedTemplate} templateBody={ selectedTemplate?.name === 'route_template' From 9867cca5e849a07498718f1ee50bcaee530a7782 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Thu, 23 May 2024 14:26:07 -0400 Subject: [PATCH 08/22] Drone -> GitHub Actions migration (#4389) # What this PR does Related to https://github.com/grafana/oncall-private/issues/2692 This PR simply deduplicates a lot of steps in our `linting-and-tests.yml` GitHub Actions workflow. This will make it much easier in `grafana/oncall-private` to be able to reuse some of these composable building blocks. --- .../install-frontend-dependencies/action.yml | 21 +++ .github/actions/setup-python/action.yml | 29 ++++ .github/workflows/e2e-tests.yml | 19 +-- .github/workflows/linting-and-tests.yml | 161 +++++------------- 4 files changed, 97 insertions(+), 133 deletions(-) create mode 100644 .github/actions/install-frontend-dependencies/action.yml create mode 100644 .github/actions/setup-python/action.yml diff --git a/.github/actions/install-frontend-dependencies/action.yml b/.github/actions/install-frontend-dependencies/action.yml new file mode 100644 index 00000000..c89dc0d6 --- /dev/null +++ b/.github/actions/install-frontend-dependencies/action.yml @@ -0,0 +1,21 @@ +name: "Install frontend dependencies" +description: "Setup node + install frontend dependencies" +runs: + using: "composite" + steps: + - uses: actions/setup-node@v3 + with: + node-version: 18.16.0 + cache: "yarn" + cache-dependency-path: grafana-plugin/yarn.lock + - name: Use cached frontend dependencies + id: cache-frontend-dependencies + uses: actions/cache@v3 + with: + path: grafana-plugin/node_modules + key: ${{ runner.os }}-frontend-node-modules-${{ hashFiles('grafana-plugin/yarn.lock') }} + - name: Install frontend dependencies + if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true' + shell: bash + working-directory: grafana-plugin + run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000 diff --git a/.github/actions/setup-python/action.yml b/.github/actions/setup-python/action.yml new file mode 100644 index 00000000..56ace557 --- /dev/null +++ b/.github/actions/setup-python/action.yml @@ -0,0 +1,29 @@ +name: "Setup Python" +description: "Setup Python + optionally install dependencies from a set of requirements file(s)" + +inputs: + install-dependencies: + description: "Whether to install dependencies from the Python requirements file(s)" + required: false + default: "true" + python-requirements-paths: + description: "The path(s) to the Python requirements file(s) to install" + required: false + default: "engine/requirements.txt engine/requirements-dev.txt" + +runs: + using: "composite" + steps: + - name: Setup Python + id: setup-python + uses: actions/setup-python@v4 + with: + python-version: "3.11.4" + cache: "pip" + cache-dependency-path: ${{ inputs.python-requirements-paths }} + - name: Install Python dependencies + if: ${{ inputs.install-dependencies == 'true' }} + shell: bash + run: | + pip install uv + uv pip sync --system ${{ inputs.python-requirements-paths }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index adccedcf..b996a7f6 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -59,11 +59,8 @@ jobs: config: ./dev/kind.yml install_only: true - - uses: actions/setup-node@v3 - with: - node-version: 18.16.0 - cache: "yarn" - cache-dependency-path: grafana-plugin/yarn.lock + - name: Install frontend dependencies + uses: ./.github/actions/install-frontend-dependencies - name: Install Tilt run: | @@ -76,18 +73,6 @@ jobs: curl -fsSL https://github.com/tilt-dev/ctlptl/releases/download/v$CTLPTL_VERSION/$CTLPTL_FILE_NAME | \ tar -xzv -C /usr/local/bin ctlptl - - name: Use cached frontend dependencies - id: cache-frontend-dependencies - uses: actions/cache@v3 - with: - path: grafana-plugin/node_modules - key: ${{ runner.os }}-frontend-node-modules-${{ hashFiles('grafana-plugin/yarn.lock') }} - - - name: Install frontend dependencies - if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true' - working-directory: grafana-plugin - run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000 - - name: Use cached plugin frontend build id: cache-plugin-frontend uses: actions/cache@v3 diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index 188a22d3..8dedefed 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -11,6 +11,12 @@ name: Linting and Tests # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions merge_group: +env: + DJANGO_SETTINGS_MODULE: settings.ci-test + DATABASE_HOST: localhost + RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 + SLACK_CLIENT_OAUTH_ID: 1 + concurrency: # Cancel any running workflow for the same branch when new commits are pushed. # We group both by ref_name (available when CI is triggered by a push to a branch/tag) @@ -23,52 +29,24 @@ jobs: name: "Lint entire project" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt - # following 2 steps - need to install the frontend dependencies for the eslint/prettier/stylelint steps - - uses: actions/setup-node@v3 - with: - node-version: 18.16.0 - cache: "yarn" - cache-dependency-path: grafana-plugin/yarn.lock - - name: Use cached frontend dependencies - id: cache-frontend-dependencies - uses: actions/cache@v3 - with: - path: grafana-plugin/node_modules - key: ${{ runner.os }}-frontend-node-modules-${{ hashFiles('grafana-plugin/yarn.lock') }} + install-dependencies: "false" - name: Install frontend dependencies - if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true' - working-directory: grafana-plugin - run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000 + uses: ./.github/actions/install-frontend-dependencies - uses: pre-commit/action@v3.0.0 lint-test-and-build-frontend: name: "Lint, test, and build frontend" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18.16.0 - cache: "yarn" - cache-dependency-path: grafana-plugin/yarn.lock - - name: Use cached frontend dependencies - id: cache-frontend-dependencies - uses: actions/cache@v3 - with: - path: grafana-plugin/node_modules - key: ${{ runner.os }}-frontend-node-modules-${{ hashFiles('grafana-plugin/yarn.lock') }} + - name: Checkout project + uses: actions/checkout@v3 - name: Install frontend dependencies - if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true' - working-directory: grafana-plugin - run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000 + uses: ./.github/actions/install-frontend-dependencies - name: Build, lint and test frontend working-directory: grafana-plugin run: yarn lint && yarn test && yarn build @@ -93,11 +71,6 @@ jobs: lint-migrations-backend-mysql-rabbitmq: name: "Lint database migrations" runs-on: ubuntu-latest - env: - DATABASE_HOST: localhost - RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 - DJANGO_SETTINGS_MODULE: settings.ci-test - SLACK_CLIENT_OAUTH_ID: 1 services: rabbit_test: image: rabbitmq:3.12.0 @@ -114,21 +87,15 @@ jobs: ports: - 3306:3306 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python - name: Lint migrations working-directory: engine # makemigrations --check = Exit with a non-zero status if model changes are missing migrations # and don't actually write them. run: | - pip install uv - uv pip sync --system requirements.txt requirements-dev.txt python manage.py makemigrations --check python manage.py lintmigrations @@ -136,7 +103,8 @@ jobs: name: "Helm Chart Unit Tests" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout project + uses: actions/checkout@v3 - uses: azure/setup-helm@v3 with: version: v3.8.0 @@ -152,10 +120,6 @@ jobs: matrix: rbac_enabled: ["True", "False"] env: - DJANGO_SETTINGS_MODULE: settings.ci-test - DATABASE_HOST: localhost - RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 - SLACK_CLIENT_OAUTH_ID: 1 ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: rabbit_test: @@ -173,20 +137,14 @@ jobs: ports: - 3306:3306 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python - name: Unit Test Backend working-directory: engine run: | apt-get update && apt-get install -y netcat-traditional - pip install uv - uv pip sync --system requirements.txt requirements-dev.txt ./wait_for_test_mysql_start.sh && pytest -x unit-test-backend-postgresql-rabbitmq: @@ -197,10 +155,6 @@ jobs: rbac_enabled: ["True", "False"] env: DATABASE_TYPE: postgresql - DATABASE_HOST: localhost - RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 - DJANGO_SETTINGS_MODULE: settings.ci-test - SLACK_CLIENT_OAUTH_ID: 1 ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: rabbit_test: @@ -224,20 +178,13 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python - name: Unit Test Backend working-directory: engine - run: | - pip install uv - uv pip sync --system requirements.txt requirements-dev.txt - pytest -x + run: pytest -x unit-test-backend-sqlite-redis: name: "Backend Tests: SQLite + Redis (RBAC enabled: ${{ matrix.rbac_enabled }})" @@ -249,8 +196,6 @@ jobs: DATABASE_TYPE: sqlite3 BROKER_TYPE: redis REDIS_URI: redis://localhost:6379 - DJANGO_SETTINGS_MODULE: settings.ci-test - SLACK_CLIENT_OAUTH_ID: 1 ONCALL_TESTING_RBAC_ENABLED: ${{ matrix.rbac_enabled }} services: redis_test: @@ -263,57 +208,41 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python - name: Unit Test Backend working-directory: engine run: | apt-get update && apt-get install -y netcat-traditional - pip install uv - uv pip sync --system requirements.txt requirements-dev.txt pytest -x unit-test-migrators: name: "Unit tests - Migrators" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: tools/migrators/requirements.txt + python-requirements-paths: tools/migrators/requirements.txt - name: Unit Test Migrators working-directory: tools/migrators - run: | - pip install uv - uv pip sync --system requirements.txt - pytest -x + run: pytest -x mypy: name: "mypy" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.11.4" - cache: "pip" - cache-dependency-path: | - engine/requirements.txt - engine/requirements-dev.txt + - name: Checkout project + uses: actions/checkout@v3 + - name: Setup Python + uses: ./.github/actions/setup-python - name: mypy Static Type Checking working-directory: engine - run: | - pip install uv - uv pip sync --system requirements.txt requirements-dev.txt - mypy . + run: mypy . end-to-end-tests: name: Standard e2e tests From f061d26a7d7a9fdae5fdf94fd91642de5fb0d36c Mon Sep 17 00:00:00 2001 From: Dominik Broj Date: Thu, 23 May 2024 20:22:56 +0200 Subject: [PATCH 09/22] hide datasource dropdown on cloud (#4386) # What this PR does hide datasource dropdown on cloud ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- .../src/pages/insights/variables.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/src/pages/insights/variables.ts b/grafana-plugin/src/pages/insights/variables.ts index 199bcfd9..b8dbd6e2 100644 --- a/grafana-plugin/src/pages/insights/variables.ts +++ b/grafana-plugin/src/pages/insights/variables.ts @@ -15,13 +15,18 @@ const DEFAULT_VARIABLE_CONFIG: Partial ({ - datasource: new DataSourceVariable({ - name: 'datasource', - label: 'Data source', - pluginId: 'prometheus', - value: 'grafanacloud-usage', - }), +const getVariables = ({ isOpenSource, datasource, stack }: InsightsConfig) => ({ + // Selectable + ...(isOpenSource + ? { + datasource: new DataSourceVariable({ + name: 'datasource', + label: 'Data source', + pluginId: 'prometheus', + value: 'grafanacloud-usage', + }), + } + : {}), team: new QueryVariable({ ...DEFAULT_VARIABLE_CONFIG, name: 'team', From dfea60e7368ed46a712df7b71ea7b405009e702f Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Fri, 24 May 2024 11:32:19 +0300 Subject: [PATCH 10/22] Allow custom silence period for alert groups + display how much left is for a silenced AG (#4375) # What this PR does https://github.com/grafana/oncall/issues/2333 - Allow custom input for silence period (either by duration or by setting a future date in the calendar, both inputs are syncedd/editable) - Show how much time is left for a silenced alert group ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall/issues/2333 --- .../RenderConditionally.tsx | 17 +- .../src/models/alertgroup/alertgroup.ts | 2 +- .../src/pages/incident/Incident.tsx | 41 ++++- .../src/pages/incidents/Incidents.tsx | 1 + .../incidents/parts/IncidentDropdown.tsx | 152 +++++++++++------- .../incidents/parts/IncidentSilenceModal.tsx | 111 +++++++++++++ .../incidents/parts/SilenceButtonCascader.tsx | 19 ++- .../pages/incidents/parts/SilenceSelect.tsx | 39 +++-- grafana-plugin/src/styles/utils.styles.ts | 4 + 9 files changed, 300 insertions(+), 86 deletions(-) create mode 100644 grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx diff --git a/grafana-plugin/src/components/RenderConditionally/RenderConditionally.tsx b/grafana-plugin/src/components/RenderConditionally/RenderConditionally.tsx index 0983e9fc..ca2c23b9 100644 --- a/grafana-plugin/src/components/RenderConditionally/RenderConditionally.tsx +++ b/grafana-plugin/src/components/RenderConditionally/RenderConditionally.tsx @@ -2,9 +2,20 @@ import React, { FC, ReactNode } from 'react'; interface RenderConditionallyProps { shouldRender?: boolean; - children: ReactNode; + children?: ReactNode; + render?: () => ReactNode; backupChildren?: ReactNode; } -export const RenderConditionally: FC = ({ shouldRender, children, backupChildren = null }) => - shouldRender ? <>{children} : <>{backupChildren}; +export const RenderConditionally: FC = ({ + shouldRender, + children, + render, + backupChildren = null, +}) => { + if (render) { + return shouldRender ? <>{render()} : <>{backupChildren}; + } + + return shouldRender ? <>{children} : <>{backupChildren}; +}; diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.ts b/grafana-plugin/src/models/alertgroup/alertgroup.ts index 52031f38..abb53c14 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.ts @@ -24,7 +24,7 @@ export class AlertGroupStore { rootStore: RootStore; alerts = new Map(); bulkActions: any = []; - silenceOptions: any; + silenceOptions: Array; searchResult: { [key: string]: Array } = {}; incidentFilters: any; initialQuery = qs.parse(window.location.search); diff --git a/grafana-plugin/src/pages/incident/Incident.tsx b/grafana-plugin/src/pages/incident/Incident.tsx index 89b9f547..5f01d877 100644 --- a/grafana-plugin/src/pages/incident/Incident.tsx +++ b/grafana-plugin/src/pages/incident/Incident.tsx @@ -36,6 +36,7 @@ import { initErrorDataState, } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers'; import { PluginLink } from 'components/PluginLink/PluginLink'; +import { RenderConditionally } from 'components/RenderConditionally/RenderConditionally'; import { SourceCode } from 'components/SourceCode/SourceCode'; import { Text } from 'components/Text/Text'; import { TooltipBadge } from 'components/TooltipBadge/TooltipBadge'; @@ -49,7 +50,8 @@ import { AlertGroupHelper } from 'models/alertgroup/alertgroup.helpers'; import { AlertAction, TimeLineItem, TimeLineRealm, GroupedAlert } from 'models/alertgroup/alertgroup.types'; import { ResolutionNoteSourceTypesToDisplayName } from 'models/resolution_note/resolution_note.types'; import { ApiSchemas } from 'network/oncall-api/api.types'; -import { IncidentDropdown } from 'pages/incidents/parts/IncidentDropdown'; +import { CUSTOM_SILENCE_VALUE, IncidentDropdown } from 'pages/incidents/parts/IncidentDropdown'; +import { IncidentSilenceModal } from 'pages/incidents/parts/IncidentSilenceModal'; import { AppFeature } from 'state/features'; import { PageProps, WithStoreProps } from 'state/types'; import { useStore } from 'state/useStore'; @@ -73,6 +75,7 @@ interface IncidentPageState extends PageBaseState { showAttachIncidentForm?: boolean; timelineFilter: string; resolutionNoteText: string; + silenceModalData: { incident: ApiSchemas['AlertGroup'] }; } @observer @@ -81,6 +84,7 @@ class _IncidentPage extends React.Component )} + + {/* Modal where users can input their custom duration for silencing an alert group */} + ( + this.setState({ silenceModalData: undefined })} + onSave={(duration: number) => { + this.setState({ silenceModalData: undefined }); + store.alertGroupStore.doIncidentAction(silenceModalData.incident.pk, AlertAction.Silence, duration); + }} + /> + )} + /> )} @@ -342,7 +363,7 @@ class _IncidentPage extends React.Component @@ -437,7 +458,7 @@ class _IncidentPage extends React.Component { + getSilenceClickHandler = (incident: ApiSchemas['AlertGroup']) => { const { store } = this.props; - return (value: number) => { - return store.alertGroupStore.doIncidentAction(incidentId, AlertAction.Silence, value); + return (value: number): Promise => { + if (value === CUSTOM_SILENCE_VALUE) { + this.setState({ silenceModalData: { incident } }); + return Promise.resolve(); // awaited by other component + } + return store.alertGroupStore.doIncidentAction(incident.pk, AlertAction.Silence, value); }; }; diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index cee71fa2..044984de 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -184,6 +184,7 @@ class _IncidentsPage extends React.Component + {showAddAlertGroupForm && ( { diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index d31399f0..fd2d0350 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -1,7 +1,8 @@ import React, { FC, SyntheticEvent, useRef, useState } from 'react'; import { cx } from '@emotion/css'; -import { Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; +import { intervalToAbbreviatedDurationString } from '@grafana/data'; +import { Icon, LoadingPlaceholder, Tooltip, useStyles2 } from '@grafana/ui'; import { getUtilStyles } from 'styles/utils.styles'; import { Tag, TagColor } from 'components/Tag/Tag'; @@ -13,6 +14,7 @@ import { ApiSchemas } from 'network/oncall-api/api.types'; import { UserActions } from 'utils/authorization/authorization'; import { getIncidentDropdownStyles } from './IncidentDropdown.styles'; +import { IncidentSilenceModal } from './IncidentSilenceModal'; import { SilenceSelect } from './SilenceSelect'; const getIncidentTagColor = (alert: ApiSchemas['AlertGroup']) => { @@ -55,6 +57,8 @@ function IncidentStatusTag({ ); } +export const CUSTOM_SILENCE_VALUE = -100; + export const IncidentDropdown: FC<{ alert: ApiSchemas['AlertGroup']; onResolve: (e: SyntheticEvent) => Promise; @@ -67,6 +71,7 @@ export const IncidentDropdown: FC<{ const [isLoading, setIsLoading] = useState(false); const [currentLoadingAction, setCurrentActionLoading] = useState(undefined); const [forcedOpenAction, setForcedOpenAction] = useState(undefined); + const [isSilenceModalOpen, setIsSilenceModalOpen] = useState(false); const styles = useStyles2(getIncidentDropdownStyles); const utilStyles = useStyles2(getUtilStyles); @@ -160,60 +165,79 @@ export const IncidentDropdown: FC<{ if (alert.status === IncidentStatus.Firing) { return ( - ( -
- -
onClickFn(e, AlertAction.unResolve, onAcknowledge, IncidentStatus.Acknowledged)} - > - Acknowledge{' '} - {currentLoadingAction === IncidentStatus.Acknowledged && isLoading && ( - - - - )} + <> + ( +
+ +
onClickFn(e, AlertAction.unResolve, onAcknowledge, IncidentStatus.Acknowledged)} + > + Acknowledge{' '} + {currentLoadingAction === IncidentStatus.Acknowledged && isLoading && ( + + + + )} +
+
+ +
onClickFn(e, AlertAction.unResolve, onResolve, IncidentStatus.Resolved)} + > + Resolve{' '} + {currentLoadingAction === IncidentStatus.Resolved && isLoading && ( + + + + )} +
+
+ +
+ { + if (value === CUSTOM_SILENCE_VALUE) { + return setIsSilenceModalOpen(true); + } + + setIsLoading(true); + setForcedOpenAction(AlertAction.unResolve); + setCurrentActionLoading(IncidentStatus.Silenced); + + await onSilence(value); + + setIsLoading(false); + setForcedOpenAction(undefined); + setCurrentActionLoading(undefined); + }} + />
- - -
onClickFn(e, AlertAction.unResolve, onResolve, IncidentStatus.Resolved)} - > - Resolve{' '} - {currentLoadingAction === IncidentStatus.Resolved && isLoading && ( - - - - )} -
-
- -
- { - setIsLoading(true); - setForcedOpenAction(AlertAction.unResolve); - setCurrentActionLoading(IncidentStatus.Silenced); - - await onSilence(value); - - setIsLoading(false); - setForcedOpenAction(undefined); - setCurrentActionLoading(undefined); - }} - />
-
- )} - > - {({ openMenu }) => } -
+ )} + > + {({ openMenu }) => } + + setIsSilenceModalOpen(false)} + onSave={async (value) => { + setIsSilenceModalOpen(false); + setIsLoading(true); + await onSilence(value); + setIsLoading(false); + }} + /> + ); } @@ -265,7 +289,27 @@ export const IncidentDropdown: FC<{
)} > - {({ openMenu }) => } + {({ openMenu }) => ( + + + + + + )} ); }; + +function getSilencedTooltip(alert: ApiSchemas['AlertGroup']) { + if (alert.silenced_until === null) { + return `Silenced forever`; + } + return `Silence ends in ${getSilencedUntilInDuration(alert.silenced_until)}`; +} + +function getSilencedUntilInDuration(date: string) { + return intervalToAbbreviatedDurationString({ + start: new Date(), + end: new Date(date), + }); +} diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx new file mode 100644 index 00000000..baf804ff --- /dev/null +++ b/grafana-plugin/src/pages/incidents/parts/IncidentSilenceModal.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; + +import { css } from '@emotion/css'; +import { + DateTime, + addDurationToDate, + dateTime, + durationToMilliseconds, + intervalToAbbreviatedDurationString, + isValidDuration, + parseDuration, +} from '@grafana/data'; +import { Button, DateTimePicker, Field, HorizontalGroup, Input, Modal, useStyles2 } from '@grafana/ui'; + +import { useDebouncedCallback } from 'utils/hooks'; + +interface IncidentSilenceModalProps { + isOpen: boolean; + alertGroupID: string; + alertGroupName: string; + + onDismiss: () => void; + onSave: (value: number) => void; +} + +const IncidentSilenceModal: React.FC = ({ + isOpen, + alertGroupID, + alertGroupName, + + onDismiss, + onSave, +}) => { + const [date, setDate] = useState(dateTime('2021-05-05 12:00:00')); + const [duration, setDuration] = useState(''); + const debouncedUpdateDateTime = useDebouncedCallback(updateDateTime, 500); + + const styles = useStyles2(getStyles); + const isDurationValid = isValidDuration(duration); + + return ( + +
+ + + + + + + +
+ + + + + +
+ ); + + function onDateChange(date: DateTime) { + setDate(date); + const duration = intervalToAbbreviatedDurationString({ + start: new Date(), + end: new Date(date.toDate()), + }); + setDuration(duration); + } + + function onDurationChange(event: React.SyntheticEvent) { + const newDuration = event.currentTarget.value; + if (newDuration !== duration) { + setDuration(newDuration); + debouncedUpdateDateTime(newDuration); + } + } + + function updateDateTime(newDuration: string) { + setDate(dateTime(addDurationToDate(new Date(), parseDuration(newDuration)))); + } + + function onSubmit() { + onSave(durationToMilliseconds(parseDuration(duration)) / 1000); + } +}; + +const getStyles = () => ({ + root: css` + width: 600px; + `, + + container: css` + width: 100%; + display: flex; + column-gap: 16px; + `, + containerChild: css` + flex-grow: 1; + `, +}); + +export { IncidentSilenceModal }; diff --git a/grafana-plugin/src/pages/incidents/parts/SilenceButtonCascader.tsx b/grafana-plugin/src/pages/incidents/parts/SilenceButtonCascader.tsx index abbb1360..89056dee 100644 --- a/grafana-plugin/src/pages/incidents/parts/SilenceButtonCascader.tsx +++ b/grafana-plugin/src/pages/incidents/parts/SilenceButtonCascader.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { ButtonCascader, ComponentSize } from '@grafana/ui'; +import { ButtonCascader, CascaderOption, ComponentSize } from '@grafana/ui'; import { observer } from 'mobx-react'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; @@ -8,6 +8,8 @@ import { SelectOption } from 'state/types'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization/authorization'; +import { CUSTOM_SILENCE_VALUE } from './IncidentDropdown'; + interface SilenceButtonCascaderProps { className?: string; disabled?: boolean; @@ -38,10 +40,15 @@ export const SilenceButtonCascader = observer((props: SilenceButtonCascaderProps
); - function getOptions() { - return silenceOptions.map((silenceOption: SelectOption) => ({ - value: silenceOption.value, - label: silenceOption.display_name, - })); + function getOptions(): CascaderOption[] { + return silenceOptions + .map((silenceOption: SelectOption) => ({ + value: silenceOption.value, + label: silenceOption.display_name, + })) + .concat({ + value: CUSTOM_SILENCE_VALUE, + label: 'Custom', + }) as CascaderOption[]; } }); diff --git a/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx b/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx index 8de2a75c..fd83a7dd 100644 --- a/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx +++ b/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx @@ -10,12 +10,13 @@ import { UserActions } from 'utils/authorization/authorization'; interface SilenceSelectProps { placeholder?: string; + customValueNum: number; onSelect: (value: number) => void; } export const SilenceSelect = observer((props: SilenceSelectProps) => { - const { placeholder = 'Silence for', onSelect } = props; + const { customValueNum, placeholder = 'Silence for', onSelect } = props; const store = useStore(); @@ -24,21 +25,31 @@ export const SilenceSelect = observer((props: SilenceSelectProps) => { const silenceOptions = alertGroupStore.silenceOptions || []; return ( - - { + onSelect(Number(value)); + }} + options={getOptions()} + /> + + ); function getOptions() { - return silenceOptions.map((silenceOption: SelectOption) => ({ - value: silenceOption.value, - label: silenceOption.display_name, - })); + return silenceOptions + .map((silenceOption: SelectOption) => ({ + value: silenceOption.value, + label: silenceOption.display_name, + })) + .concat({ + value: customValueNum, + label: 'Custom', + }); } }); diff --git a/grafana-plugin/src/styles/utils.styles.ts b/grafana-plugin/src/styles/utils.styles.ts index cdcc413b..fc84e0d8 100644 --- a/grafana-plugin/src/styles/utils.styles.ts +++ b/grafana-plugin/src/styles/utils.styles.ts @@ -4,6 +4,10 @@ import tinycolor from 'tinycolor2'; export const getUtilStyles = (theme: GrafanaTheme2) => { return { + flex: css` + display: flex; + `, + width100: css` width: 100%; `, From 93ab3407ff343fc9714706cefeb8fe1db7788dbc Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 24 May 2024 09:55:08 -0400 Subject: [PATCH 11/22] rename settings/ci-test.py to settings/ci_test.py (#4391) # What this PR does Related to Drone -> GitHub Actions migration --- .drone.yml | 4 ++-- .github/workflows/linting-and-tests.yml | 2 +- Makefile | 4 ++-- engine/apps/mobile_app/tests/test_alert_rendering.py | 2 +- engine/settings/{ci-test.py => ci_test.py} | 0 5 files changed, 6 insertions(+), 6 deletions(-) rename engine/settings/{ci-test.py => ci_test.py} (100%) diff --git a/.drone.yml b/.drone.yml index 21678bc6..a922bff0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -53,7 +53,7 @@ steps: - name: Lint Backend image: python:3.11.4 environment: - DJANGO_SETTINGS_MODULE: settings.ci-test + DJANGO_SETTINGS_MODULE: settings.ci_test commands: - pip install $(grep "pre-commit==" engine/requirements-dev.txt) - pre-commit run isort --all-files @@ -64,7 +64,7 @@ steps: image: python:3.11.4 environment: RABBITMQ_URI: amqp://rabbitmq:rabbitmq@rabbit_test:5672 - DJANGO_SETTINGS_MODULE: settings.ci-test + DJANGO_SETTINGS_MODULE: settings.ci_test SLACK_CLIENT_OAUTH_ID: 1 commands: - apt-get update && apt-get install -y netcat-traditional diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index 8dedefed..d41421b1 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -12,7 +12,7 @@ name: Linting and Tests merge_group: env: - DJANGO_SETTINGS_MODULE: settings.ci-test + DJANGO_SETTINGS_MODULE: settings.ci_test DATABASE_HOST: localhost RABBITMQ_URI: amqp://rabbitmq:rabbitmq@localhost:5672 SLACK_CLIENT_OAUTH_ID: 1 diff --git a/Makefile b/Makefile index 46ee7d89..fa4f5a1a 100644 --- a/Makefile +++ b/Makefile @@ -108,10 +108,10 @@ define run_ui_docker_command $(call run_docker_compose_command,run --rm oncall_ui sh -c '$(1)') endef -# always use settings.ci-test django settings file when running the tests +# always use settings.ci_test django settings file when running the tests # if we use settings.dev it's very possible that some fail just based on the settings alone define run_backend_tests - $(call run_engine_docker_command,pytest --ds=settings.ci-test $(1)) + $(call run_engine_docker_command,pytest --ds=settings.ci_test $(1)) endef .PHONY: local/up diff --git a/engine/apps/mobile_app/tests/test_alert_rendering.py b/engine/apps/mobile_app/tests/test_alert_rendering.py index 6b8c39d9..f96df843 100644 --- a/engine/apps/mobile_app/tests/test_alert_rendering.py +++ b/engine/apps/mobile_app/tests/test_alert_rendering.py @@ -8,7 +8,7 @@ from apps.mobile_app.backend import MobileAppBackend MAX_ALERT_TITLE_LENGTH = 200 -# this is a dirty hack to get around EXTRA_MESSAGING_BACKENDS being set in settings/ci-test.py +# this is a dirty hack to get around EXTRA_MESSAGING_BACKENDS being set in settings/ci_test.py # we can't simply change the value because 100s of tests fail as they rely on the value being set to a specific value 🫠 # see where this value is used in the unitest.mock.patch calls down below for more context backend = MobileAppBackend(notification_channel_id=5) diff --git a/engine/settings/ci-test.py b/engine/settings/ci_test.py similarity index 100% rename from engine/settings/ci-test.py rename to engine/settings/ci_test.py From 96a4b36f1e46a77a915331e94fdb553dfb151a03 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 24 May 2024 14:24:02 -0400 Subject: [PATCH 12/22] update drone.yml signature --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index a922bff0..956acfef 100644 --- a/.drone.yml +++ b/.drone.yml @@ -386,4 +386,4 @@ name: cloud_access_policy_token --- kind: signature -hmac: 7bf9c1d378bf2a93cb758436de78878f9a49a8501b5d1b199c412198439d3593 +hmac: c3043848d6057dfa6fb59d49459af1cbc0d013a697fd84a1329c444a6beb8ce1 From a92203e71cb2de80fe0c67ee9f8c03040b587507 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 24 May 2024 15:13:38 -0400 Subject: [PATCH 13/22] remove unnecessary CI step `bash` command (#4397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What this PR does This command is no longer necessary. Currently it doesn't seem to be working (see screenshot 👇). Presumably `netcat-traditional` is installed on the `ubuntu-latest-8-cores` runner. ![Screenshot 2024-05-24 at 14 42 23](https://github.com/grafana/oncall/assets/9406895/dcb80711-6168-4885-a7b8-a05e323c03bf) --- .github/workflows/expensive-e2e-tests.yml | 2 +- .github/workflows/linting-and-tests.yml | 8 ++------ engine/apps/grafana_plugin/tests/test_app_config.py | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/expensive-e2e-tests.yml b/.github/workflows/expensive-e2e-tests.yml index 5bda45e3..16090caf 100644 --- a/.github/workflows/expensive-e2e-tests.yml +++ b/.github/workflows/expensive-e2e-tests.yml @@ -44,7 +44,7 @@ jobs: post-status-to-slack: runs-on: ubuntu-latest needs: end-to-end-tests - if: failure + if: failure() steps: # Useful references # https://stackoverflow.com/questions/59073850/github-actions-get-url-of-test-build diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index d41421b1..2a9f3397 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -143,9 +143,7 @@ jobs: uses: ./.github/actions/setup-python - name: Unit Test Backend working-directory: engine - run: | - apt-get update && apt-get install -y netcat-traditional - ./wait_for_test_mysql_start.sh && pytest -x + run: ./wait_for_test_mysql_start.sh && pytest -x unit-test-backend-postgresql-rabbitmq: name: "Backend Tests: PostgreSQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})" @@ -214,9 +212,7 @@ jobs: uses: ./.github/actions/setup-python - name: Unit Test Backend working-directory: engine - run: | - apt-get update && apt-get install -y netcat-traditional - pytest -x + run: pytest -x unit-test-migrators: name: "Unit tests - Migrators" diff --git a/engine/apps/grafana_plugin/tests/test_app_config.py b/engine/apps/grafana_plugin/tests/test_app_config.py index d7aa5e71..80ba9f26 100644 --- a/engine/apps/grafana_plugin/tests/test_app_config.py +++ b/engine/apps/grafana_plugin/tests/test_app_config.py @@ -19,6 +19,7 @@ app_name = "grafana_plugin" ) @patch.object(sys, "exit") @override_settings(LICENSE=settings.OPEN_SOURCE_LICENSE_NAME) +@override_settings(IS_OPEN_SOURCE=True) @override_settings(SELF_HOSTED_SETTINGS={"GRAFANA_API_URL": None}) @pytest.mark.django_db def test_it_crashes_the_app_if_the_env_var_is_not_present_for_oss_installations_and_an_org_does_not_exist( From 357857facac1758bc895a03b6bbda0799d4e407d Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Fri, 24 May 2024 14:26:28 -0600 Subject: [PATCH 14/22] Add endpoint to get a single user group (#4395) # What this PR does Add endpoint to return slack user group from public primary key ## Which issue(s) this PR closes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- engine/apps/api/tests/test_user_groups.py | 27 +++++++++++++++++++++++ engine/apps/api/views/user_group.py | 5 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/engine/apps/api/tests/test_user_groups.py b/engine/apps/api/tests/test_user_groups.py index 28176b56..c24bb3f7 100644 --- a/engine/apps/api/tests/test_user_groups.py +++ b/engine/apps/api/tests/test_user_groups.py @@ -72,3 +72,30 @@ def test_usergroup_permissions( response = client.get(url, format="json", **make_user_auth_headers(user, token)) assert response.status_code == expected_status + + +@pytest.mark.django_db +def test_get_usergroup( + make_slack_team_identity, + make_slack_user_group, + make_organization, + make_user_for_organization, + make_token_for_organization, + make_user_auth_headers, +): + team_identity = make_slack_team_identity() + user_group = make_slack_user_group( + slack_team_identity=team_identity, name="Test User Group", handle="test-user-group" + ) + + organization = make_organization(slack_team_identity=team_identity) + _, token = make_token_for_organization(organization=organization) + user = make_user_for_organization(organization=organization) + + client = APIClient() + url = reverse("api-internal:user_group-detail", kwargs={"pk": user_group.public_primary_key}) + response = client.get(url, format="json", **make_user_auth_headers(user, token)) + expected_data = {"id": user_group.public_primary_key, "name": "Test User Group", "handle": "test-user-group"} + + assert response.status_code == status.HTTP_200_OK + assert response.data == expected_data diff --git a/engine/apps/api/views/user_group.py b/engine/apps/api/views/user_group.py index 296fcf1e..31ccfea8 100644 --- a/engine/apps/api/views/user_group.py +++ b/engine/apps/api/views/user_group.py @@ -6,9 +6,12 @@ from apps.api.permissions import RBACPermission from apps.api.serializers.user_group import UserGroupSerializer from apps.auth_token.auth import PluginAuthentication from apps.slack.models import SlackUserGroup +from common.api_helpers.mixins import PublicPrimaryKeyMixin -class UserGroupViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): +class UserGroupViewSet( + PublicPrimaryKeyMixin[SlackUserGroup], mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet +): authentication_classes = (PluginAuthentication,) permission_classes = (IsAuthenticated, RBACPermission) serializer_class = UserGroupSerializer From 997cb64a57282fec550f24f880ec0993194985e2 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 27 May 2024 10:51:01 +0300 Subject: [PATCH 15/22] Fixed UserGroup selectors not showing the set value (#4394) # What this PR does Fix for https://github.com/grafana/oncall/issues/4371 - Fixed UserGroups' selectors not showing up the stored value - this behavior was found in Schedule Form and Escalation Chains ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall/issues/4371 --- .../src/components/Policy/EscalationPolicy.tsx | 5 ++--- .../src/containers/ScheduleForm/ScheduleForm.tsx | 4 ++-- grafana-plugin/src/models/user_group/user_group.ts | 14 +++++++++++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx index 098f040f..c977a767 100644 --- a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx @@ -383,13 +383,12 @@ class _EscalationPolicy extends React.Component { return ( - + allowClear disabled={isDisabled} items={userGroupStore.items} fetchItemsFn={userGroupStore.updateItems} - fetchItemFn={() => undefined} - // TODO: fetchItemFn + fetchItemFn={userGroupStore.fetchItemById} getSearchResult={userGroupStore.getSearchResult} displayField="name" valueField="id" diff --git a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx index 420b4776..f3248a9e 100644 --- a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx +++ b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx @@ -289,11 +289,11 @@ const ScheduleNotificationSettingsFields = () => { invalid={!!errors.user_group} error={errors.user_group?.message} > - + allowClear items={userGroupStore.items} fetchItemsFn={userGroupStore.updateItems} - fetchItemFn={() => undefined} + fetchItemFn={userGroupStore.fetchItemById} getSearchResult={userGroupStore.getSearchResult} displayField="handle" placeholder="Select User Group" diff --git a/grafana-plugin/src/models/user_group/user_group.ts b/grafana-plugin/src/models/user_group/user_group.ts index d19b6a48..d162aa7d 100644 --- a/grafana-plugin/src/models/user_group/user_group.ts +++ b/grafana-plugin/src/models/user_group/user_group.ts @@ -11,7 +11,7 @@ export class UserGroupStore extends BaseStore { searchResult: { [key: string]: Array } = {}; @observable.shallow - items?: { [id: string]: UserGroup[] } = {}; + items?: { [id: string]: UserGroup } = {}; constructor(rootStore: RootStore) { super(rootStore); @@ -46,6 +46,18 @@ export class UserGroupStore extends BaseStore { }); } + @action.bound + async fetchItemById(id: string) { + const item: UserGroup = await this.getById(id); + + runInAction(() => { + this.items = { + ...this.items, + [id]: item, + }; + }); + } + getSearchResult = (query = '') => { if (!this.searchResult[query]) { return undefined; From d52e821c33f332bb33f480ba719d85e9d67b86b7 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Mon, 27 May 2024 09:18:33 -0400 Subject: [PATCH 16/22] update install-frontend-dependencies github action (#4402) --- .../actions/install-frontend-dependencies/action.yml | 11 ++++++++--- .github/actions/setup-python/action.yml | 2 -- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/actions/install-frontend-dependencies/action.yml b/.github/actions/install-frontend-dependencies/action.yml index c89dc0d6..a979ef43 100644 --- a/.github/actions/install-frontend-dependencies/action.yml +++ b/.github/actions/install-frontend-dependencies/action.yml @@ -1,5 +1,10 @@ name: "Install frontend dependencies" description: "Setup node + install frontend dependencies" +inputs: + working-directory: + description: "Relative path to oncall/grafana-plugin directory" + required: false + default: "." runs: using: "composite" steps: @@ -7,15 +12,15 @@ runs: with: node-version: 18.16.0 cache: "yarn" - cache-dependency-path: grafana-plugin/yarn.lock + cache-dependency-path: ${{ inputs.working-directory }}/grafana-plugin/yarn.lock - name: Use cached frontend dependencies id: cache-frontend-dependencies uses: actions/cache@v3 with: - path: grafana-plugin/node_modules + path: ${{ inputs.working-directory }}/grafana-plugin/node_modules key: ${{ runner.os }}-frontend-node-modules-${{ hashFiles('grafana-plugin/yarn.lock') }} - name: Install frontend dependencies if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true' shell: bash - working-directory: grafana-plugin + working-directory: ${{ inputs.working-directory }}/grafana-plugin run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000 diff --git a/.github/actions/setup-python/action.yml b/.github/actions/setup-python/action.yml index 56ace557..54d9fbb0 100644 --- a/.github/actions/setup-python/action.yml +++ b/.github/actions/setup-python/action.yml @@ -1,6 +1,5 @@ name: "Setup Python" description: "Setup Python + optionally install dependencies from a set of requirements file(s)" - inputs: install-dependencies: description: "Whether to install dependencies from the Python requirements file(s)" @@ -10,7 +9,6 @@ inputs: description: "The path(s) to the Python requirements file(s) to install" required: false default: "engine/requirements.txt engine/requirements-dev.txt" - runs: using: "composite" steps: From f7beced64e0d6299eb45d3a5bd35b01fc2a30cda Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Mon, 27 May 2024 16:28:56 +0300 Subject: [PATCH 17/22] More emotionjs migration changes (#4362) # What this PR does - Get rid of some of the hardcoded css variables defined in `vars.css`, specifically tags-related, but not only - Migrated a few more components to emotion --- grafana-plugin/src/assets/style/vars.css | 32 -- .../src/components/CardButton/CardButton.tsx | 4 +- .../src/components/Collapse/Collapse.tsx | 2 +- grafana-plugin/src/components/GList/GList.tsx | 2 +- .../src/components/GTable/GTable.tsx | 8 +- .../IntegrationCollapsibleTreeView.tsx | 4 +- .../IntegrationInputField.tsx | 2 +- .../Integrations/IntegrationBlock.tsx | 2 +- .../Integrations/IntegrationBlockItem.tsx | 6 +- .../Integrations/IntegrationTemplateBlock.tsx | 6 +- .../NewScheduleSelector.tsx | 10 +- .../PageErrorHandlingWrapper.tsx | 6 +- .../ScheduleBorderedAvatar.tsx | 8 +- .../ScheduleFilters/ScheduleFilters.tsx | 4 +- .../ScheduleQuality/ScheduleQuality.tsx | 4 +- .../ScheduleQualityDetails.tsx | 32 +- .../ScheduleQualityProgressBar.tsx | 2 +- .../src/components/SourceCode/SourceCode.tsx | 4 +- grafana-plugin/src/components/Table/Table.tsx | 6 +- grafana-plugin/src/components/Tag/Tag.tsx | 5 +- .../components/TooltipBadge/TooltipBadge.tsx | 2 +- .../src/components/Tutorial/Tutorial.tsx | 18 +- .../components/Unauthorized/Unauthorized.tsx | 6 +- .../src/components/UserGroups/UserGroups.tsx | 14 +- .../VerticalTabsBar/VerticalTabsBar.tsx | 2 +- .../AddResponders/AddResponders.module.scss | 65 ---- .../AddResponders/AddResponders.styles.ts | 77 ++++ .../AddResponders/AddResponders.tsx | 51 +-- .../__snapshots__/AddResponders.test.tsx.snap | 32 +- .../parts/TeamResponder/TeamResponder.tsx | 45 +-- .../__snapshots__/TeamResponder.test.tsx.snap | 4 +- .../parts/UserResponder/UserResponder.tsx | 60 ++-- .../__snapshots__/UserResponder.test.tsx.snap | 4 +- .../containers/Rotations/Rotations.module.css | 91 ----- .../containers/Rotations/Rotations.styles.ts | 93 +++++ .../src/containers/Rotations/Rotations.tsx | 56 +-- .../containers/Rotations/ScheduleFinal.tsx | 26 +- .../Rotations/ScheduleOverrides.tsx | 35 +- .../containers/Rotations/SchedulePersonal.tsx | 26 +- .../ScheduleSlot/ScheduleSlot.module.css | 128 ------- .../containers/ScheduleSlot/ScheduleSlot.tsx | 203 +++++++++-- grafana-plugin/src/navbar/Header/Header.tsx | 18 +- .../EscalationChains.module.css | 50 --- .../EscalationChains.styles.ts | 61 ++++ .../escalation-chains/EscalationChains.tsx | 50 +-- .../src/pages/incident/Incident.helpers.tsx | 43 ++- .../src/pages/incident/Incident.module.scss | 209 ----------- .../src/pages/incident/Incident.tsx | 328 +++++++++++++++--- .../src/pages/incidents/Incidents.module.scss | 102 ------ .../src/pages/incidents/Incidents.tsx | 199 +++++++++-- .../incidents/parts/IncidentDropdown.tsx | 38 +- .../integrations/Integrations.module.scss | 59 ---- .../integrations/Integrations.styles.tsx | 71 ++++ .../src/pages/integrations/Integrations.tsx | 89 +++-- .../OutgoingWebhooks.module.scss | 17 - .../outgoing_webhooks/OutgoingWebhooks.tsx | 62 +++- .../src/pages/schedule/Schedule.module.css | 44 --- .../src/pages/schedule/Schedule.styles.ts | 45 +++ .../src/pages/schedule/Schedule.tsx | 42 ++- .../src/pages/schedules/Schedules.module.css | 41 --- .../src/pages/schedules/Schedules.styles.ts | 44 +++ .../src/pages/schedules/Schedules.tsx | 56 +-- .../pages/settings/SettingsPage.module.css | 3 - .../src/pages/settings/SettingsPage.tsx | 31 +- .../src/pages/users/Users.module.css | 67 ---- .../src/pages/users/Users.styles.ts | 74 ++++ grafana-plugin/src/pages/users/Users.tsx | 41 ++- grafana-plugin/src/styles/utils.styles.ts | 52 ++- 68 files changed, 1674 insertions(+), 1449 deletions(-) delete mode 100644 grafana-plugin/src/containers/AddResponders/AddResponders.module.scss create mode 100644 grafana-plugin/src/containers/AddResponders/AddResponders.styles.ts create mode 100644 grafana-plugin/src/containers/Rotations/Rotations.styles.ts delete mode 100644 grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css delete mode 100644 grafana-plugin/src/pages/escalation-chains/EscalationChains.module.css create mode 100644 grafana-plugin/src/pages/escalation-chains/EscalationChains.styles.ts delete mode 100644 grafana-plugin/src/pages/incident/Incident.module.scss delete mode 100644 grafana-plugin/src/pages/incidents/Incidents.module.scss delete mode 100644 grafana-plugin/src/pages/integrations/Integrations.module.scss create mode 100644 grafana-plugin/src/pages/integrations/Integrations.styles.tsx delete mode 100644 grafana-plugin/src/pages/outgoing_webhooks/OutgoingWebhooks.module.scss delete mode 100644 grafana-plugin/src/pages/schedule/Schedule.module.css create mode 100644 grafana-plugin/src/pages/schedule/Schedule.styles.ts delete mode 100644 grafana-plugin/src/pages/schedules/Schedules.module.css create mode 100644 grafana-plugin/src/pages/schedules/Schedules.styles.ts delete mode 100644 grafana-plugin/src/pages/settings/SettingsPage.module.css delete mode 100644 grafana-plugin/src/pages/users/Users.module.css create mode 100644 grafana-plugin/src/pages/users/Users.styles.ts diff --git a/grafana-plugin/src/assets/style/vars.css b/grafana-plugin/src/assets/style/vars.css index fbd53fca..199730ed 100644 --- a/grafana-plugin/src/assets/style/vars.css +++ b/grafana-plugin/src/assets/style/vars.css @@ -7,12 +7,6 @@ --always-gray: #ccccdc; --title-marginBottom: 16px; --opacity: 0.5; - --tag-danger: #e02f44; - --tag-warning: #c69b06; - --tag-primary: #299c46; - --tag-secondary: #464c54; - --tag-secondary-transparent: rgba(204, 204, 220, 0.07); - --tag-border-link: rgba(56, 113, 220, 0.2); } .theme-light { @@ -32,25 +26,12 @@ --oncall-icon-stroke-color: #fff; --hover-selected: #f4f5f5; --background-canvas: #f4f5f5; - --background-primary: #fff; --background-secondary: #f4f5f5; --border-medium-color: rgba(36, 41, 46, 0.3); --border-medium: 1px solid rgba(36, 41, 46, 0.3); --border-strong: 1px solid rgba(36, 41, 46, 0.4); --border-weak: 1px solid rgba(36, 41, 46, 0.12); --shadows-z3: 0 13px 20px 1px rgba(24, 26, 27, 0.18); - --tag-background-primary: rgba(50, 116, 217, 0.15); - --tag-border-primary: rgb(136, 174, 233); - --tag-text-primary: rgb(26, 71, 139); - --tag-background-warning: rgba(255, 120, 10, 0.15); - --tag-border-warning: rgb(255, 176, 112); - --tag-text-warning: rgb(163, 73, 0); - --tag-background-success: rgba(86, 166, 75, 0.15); - --tag-border-success: rgb(148, 203, 140); - --tag-text-success: rgb(50, 96, 43); - --tag-background-danger: rgba(224, 47, 68, 0.15); - --tag-border-danger: rgb(237, 136, 148); - --tag-text-danger: rgb(147, 22, 37); --button-background: rgba(36, 41, 46, 0.08); --button-hover-background: rgba(36, 41, 46, 0.15); --box-background: rgba(244, 245, 245); @@ -79,25 +60,12 @@ --hover-selected-hardcoded: #34363d; --oncall-icon-stroke-color: #181b1f; --background-canvas: #111217; - --background-primary: #181b1f; --background-secondary: #22252b; --border-medium-color: rgba(204, 204, 220, 0.15); --border-medium: 1px solid rgba(204, 204, 220, 0.15); --border-strong: 1px solid rgba(204, 204, 220, 0.25); --border-weak: 1px solid rgba(204, 204, 220, 0.07); --shadows-z3: 0 8px 24px rgb(1, 4, 9); - --tag-background-primary: rgba(87, 148, 242, 0.15); - --tag-border-primary: rgb(13, 72, 163); - --tag-text-primary: rgb(158, 193, 247); - --tag-background-warning: rgba(255, 152, 48, 0.15); - --tag-border-warning: rgb(150, 75, 0); - --tag-text-warning: rgb(255, 190, 124); - --tag-background-success: rgba(115, 191, 105, 0.15); - --tag-border-success: rgb(49, 100, 43); - --tag-text-success: rgb(165, 214, 159); - --tag-background-danger: rgba(242, 73, 92, 0.15); - --tag-border-danger: rgb(151, 11, 27); - --tag-text-danger: rgb(247, 144, 156); --box-background: rgba(10, 10, 10, 0.4); --working-hours-shades-color: rgba(17, 18, 23, 0.15); --working-hours-shades-color-light: rgba(17, 18, 23, 0.1); diff --git a/grafana-plugin/src/components/CardButton/CardButton.tsx b/grafana-plugin/src/components/CardButton/CardButton.tsx index f3087e4e..c7a3303e 100644 --- a/grafana-plugin/src/components/CardButton/CardButton.tsx +++ b/grafana-plugin/src/components/CardButton/CardButton.tsx @@ -28,8 +28,8 @@ export const CardButton: FC = (props) => { className={cx(styles.root, { [styles.rootSelected]: selected })} data-testid="test__cardButton" > -
{icon}
-
+
{icon}
+
{description} {title} diff --git a/grafana-plugin/src/components/Collapse/Collapse.tsx b/grafana-plugin/src/components/Collapse/Collapse.tsx index 7595634c..6d5a296d 100644 --- a/grafana-plugin/src/components/Collapse/Collapse.tsx +++ b/grafana-plugin/src/components/Collapse/Collapse.tsx @@ -50,7 +50,7 @@ export const Collapse: FC = (props) => { data-testid="test__toggle" > -
{label}
+
{label}
{isOpen && (
diff --git a/grafana-plugin/src/components/GList/GList.tsx b/grafana-plugin/src/components/GList/GList.tsx index c26bb87b..4fcdf7c6 100644 --- a/grafana-plugin/src/components/GList/GList.tsx +++ b/grafana-plugin/src/components/GList/GList.tsx @@ -64,7 +64,7 @@ export const GList = (props: GListProps) => { } return ( -
+
{items ? ( items.map((item) => (
(props: key: 'check', title: ( 0 && rowSelection.selectedRowKeys.length === data?.length} /> @@ -124,7 +124,7 @@ export const GTable = (props: render: (item: any) => { return ( @@ -136,7 +136,7 @@ export const GTable = (props: }, [rowSelection, columnsProp, data]); return ( -
+
expandable={expandable} rowKey={rowKey} @@ -148,7 +148,7 @@ export const GTable = (props: {...restProps} /> {pagination && ( -
+
)} diff --git a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx index 9cadb0e5..1d50da08 100644 --- a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx +++ b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx @@ -120,7 +120,7 @@ const IntegrationCollapsibleTreeItem: React.FC<{ return (
+ {elementPosition} ); diff --git a/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx b/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx index 25bee117..885b8f00 100644 --- a/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx +++ b/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx @@ -35,7 +35,7 @@ export const IntegrationInputField: React.FC = ({ return (
-
{renderInputField()}
+
{renderInputField()}
diff --git a/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx b/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx index 4e19558e..b9c68052 100644 --- a/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx +++ b/grafana-plugin/src/components/Integrations/IntegrationBlock.tsx @@ -41,7 +41,7 @@ export const IntegrationBlock: React.FC = ({ )} {content && ( -
+
{content}
)} diff --git a/grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx b/grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx index 13c3b81f..26849a84 100644 --- a/grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx +++ b/grafana-plugin/src/components/Integrations/IntegrationBlockItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; @@ -12,8 +12,8 @@ export const IntegrationBlockItem: React.FC = (props) const styles = useStyles2(getStyles); return ( -
-
{props.children}
+
+
{props.children}
); }; diff --git a/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx b/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx index 2280e192..d25ea77a 100644 --- a/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx +++ b/grafana-plugin/src/components/Integrations/IntegrationTemplateBlock.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, InlineLabel, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; @@ -41,11 +41,11 @@ export const IntegrationTemplateBlock: React.FC = } return ( -
+
{label} -
+
{renderInput()} {isTemplateEditable && ( <> diff --git a/grafana-plugin/src/components/NewScheduleSelector/NewScheduleSelector.tsx b/grafana-plugin/src/components/NewScheduleSelector/NewScheduleSelector.tsx index 28a1ba09..d7318e86 100644 --- a/grafana-plugin/src/components/NewScheduleSelector/NewScheduleSelector.tsx +++ b/grafana-plugin/src/components/NewScheduleSelector/NewScheduleSelector.tsx @@ -1,6 +1,6 @@ import React, { FC, useCallback, useState } from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { Button, Drawer, HorizontalGroup, Icon, VerticalGroup, useStyles2 } from '@grafana/ui'; import { Block } from 'components/GBlock/Block'; @@ -33,9 +33,9 @@ export const NewScheduleSelector: FC = ({ onHide, onCr return ( -
+
- + @@ -53,7 +53,7 @@ export const NewScheduleSelector: FC = ({ onHide, onCr - + @@ -69,7 +69,7 @@ export const NewScheduleSelector: FC = ({ onHide, onCr - + diff --git a/grafana-plugin/src/components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.tsx b/grafana-plugin/src/components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.tsx index 8d9409e9..814f8e9d 100644 --- a/grafana-plugin/src/components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.tsx +++ b/grafana-plugin/src/components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { VerticalGroup, useStyles2 } from '@grafana/ui'; @@ -52,9 +52,9 @@ export const PageErrorHandlingWrapper = function ({ const { wrongTeamNoPermissions } = errorData; return ( -
+
- + 403 {wrongTeamNoPermissions && ( diff --git a/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.tsx b/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.tsx index ccc8b722..ffa20a57 100644 --- a/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.tsx +++ b/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { useStyles2 } from '@grafana/ui'; interface ScheduleBorderedAvatarProps { @@ -20,13 +20,13 @@ export const ScheduleBorderedAvatar = function ({ }: ScheduleBorderedAvatarProps) { const styles = useStyles2(getStyles); - return
{renderSVG()}
; + return
{renderSVG()}
; function renderAvatarIcon() { return ( <> -
{renderAvatar()}
-
{renderIcon()}
+
{renderAvatar()}
+
{renderIcon()}
); } diff --git a/grafana-plugin/src/components/ScheduleFilters/ScheduleFilters.tsx b/grafana-plugin/src/components/ScheduleFilters/ScheduleFilters.tsx index 92c87ddb..c43ff373 100644 --- a/grafana-plugin/src/components/ScheduleFilters/ScheduleFilters.tsx +++ b/grafana-plugin/src/components/ScheduleFilters/ScheduleFilters.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { InlineSwitch, useStyles2 } from '@grafana/ui'; @@ -35,7 +35,7 @@ export const ScheduleFilters = (props: SchedulesFiltersProps) => { ); return ( -
+
= observer(({ schedule }) return ( <> -
+
{relatedScheduleEscalationChains?.length > 0 && schedule?.number_of_escalation_chains > 0 && ( = observer(({ schedule }) content={} >
- + Quality: {getScheduleQualityString(quality.total_score)}
diff --git a/grafana-plugin/src/components/ScheduleQualityDetails/ScheduleQualityDetails.tsx b/grafana-plugin/src/components/ScheduleQualityDetails/ScheduleQualityDetails.tsx index b64e86a6..43e9f3cd 100644 --- a/grafana-plugin/src/components/ScheduleQualityDetails/ScheduleQualityDetails.tsx +++ b/grafana-plugin/src/components/ScheduleQualityDetails/ScheduleQualityDetails.tsx @@ -30,12 +30,12 @@ export const ScheduleQualityDetails: FC = ({ qualit const warningComments = comments.filter((c) => c.type === 'warning'); return ( -
-
+
+
- + Schedule quality:{' '} - + {getScheduleQualityString(score)} @@ -53,10 +53,10 @@ export const ScheduleQualityDetails: FC = ({ qualit <> {/* Show Info comments */} {infoComments?.length > 0 && ( -
-
+
+
-
+
{infoComments.map((comment, index) => ( {comment.text} @@ -69,10 +69,10 @@ export const ScheduleQualityDetails: FC = ({ qualit {/* Show Warning comments afterwards */} {warningComments?.length > 0 && ( -
-
+
+
-
+
Rotation structure issues {warningComments.map((comment, index) => ( @@ -87,13 +87,13 @@ export const ScheduleQualityDetails: FC = ({ qualit )} {overloaded_users?.length > 0 && ( -
-
+
+
-
+
Overloaded users {overloaded_users.map((overloadedUser, index) => ( - + {overloadedUser.username} (+{overloadedUser.score}% avg) ))} @@ -115,7 +115,7 @@ export const ScheduleQualityDetails: FC = ({ qualit - + Calculation methodology @@ -126,7 +126,7 @@ export const ScheduleQualityDetails: FC = ({ qualit /> {expanded && ( - + The next 52 weeks (~1 year) are taken into account when generating the quality report. Refer to the{' '} = ({ classNa const classList = [styles.bar, className || '']; return ( -
+
{!numTotalSteps &&
} {renderSteps(numTotalSteps, completed)}
diff --git a/grafana-plugin/src/components/SourceCode/SourceCode.tsx b/grafana-plugin/src/components/SourceCode/SourceCode.tsx index 56357f11..96158c52 100644 --- a/grafana-plugin/src/components/SourceCode/SourceCode.tsx +++ b/grafana-plugin/src/components/SourceCode/SourceCode.tsx @@ -54,7 +54,7 @@ export const SourceCode: FC = ({ {showClipboardIconOnly ? ( = ({ /> ) : (
); }, - expandedRowClassName: (_record, index) => (index % 2 === 0 ? cx(styles.rowEven) : ''), + expandedRowClassName: (_record, index) => (index % 2 === 0 ? styles.rowEven : ''), } : null; }, [expandable]); @@ -61,11 +61,11 @@ export const GTable: FC = (props) => { columns={columns} data={data} expandable={expandableFn} - rowClassName={(_record, index) => (index % 2 === 0 ? cx(styles.rowEven) : '')} + rowClassName={(_record, index) => (index % 2 === 0 ? styles.rowEven : '')} {...restProps} /> {pagination && ( -
+
)} diff --git a/grafana-plugin/src/components/Tag/Tag.tsx b/grafana-plugin/src/components/Tag/Tag.tsx index 3dc9feb4..9934c18f 100644 --- a/grafana-plugin/src/components/Tag/Tag.tsx +++ b/grafana-plugin/src/components/Tag/Tag.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { useStyles2, useTheme2 } from '@grafana/ui'; import { bem, getLabelCss } from 'styles/utils.styles'; interface TagProps { @@ -30,6 +30,7 @@ export enum TagColor { export const Tag: FC = (props) => { const { color, children, className, onClick, size = 'medium' } = props; + const theme = useTheme2(); const styles = useStyles2(getStyles); @@ -49,7 +50,7 @@ export const Tag: FC = (props) => { styles[color] : css` background-color: ${color}; - color: text; + color: ${theme.colors.primary.contrastText}; `; } diff --git a/grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx b/grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx index 858af3dc..888170e2 100644 --- a/grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx +++ b/grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx @@ -46,7 +46,7 @@ export const TooltipBadge: FC = (props) => { placement={placement || 'bottom-start'} interactive content={ -
+
{tooltipTitle} {tooltipContent && {tooltipContent}} diff --git a/grafana-plugin/src/components/Tutorial/Tutorial.tsx b/grafana-plugin/src/components/Tutorial/Tutorial.tsx index f6bb5ee2..0ad6a337 100644 --- a/grafana-plugin/src/components/Tutorial/Tutorial.tsx +++ b/grafana-plugin/src/components/Tutorial/Tutorial.tsx @@ -26,10 +26,10 @@ export const Tutorial: FC = (props) => { const styles = useStyles2(getStyles); return ( - -
{title}
-
-
+ +
{title}
+
+
@@ -38,7 +38,7 @@ export const Tutorial: FC = (props) => { Add integration with a monitoring system
-
+
@@ -47,7 +47,7 @@ export const Tutorial: FC = (props) => { Setup escalation chain to handle notifications
-
+
@@ -56,7 +56,7 @@ export const Tutorial: FC = (props) => { Connect to your chat workspace
-
+
@@ -65,7 +65,7 @@ export const Tutorial: FC = (props) => { Add your team calendar to define an on-call rotation.
-
+
@@ -81,7 +81,7 @@ export const Tutorial: FC = (props) => { const Arrow = () => { const styles = useStyles2(getStyles); return ( -
+
diff --git a/grafana-plugin/src/components/Unauthorized/Unauthorized.tsx b/grafana-plugin/src/components/Unauthorized/Unauthorized.tsx index 58c02971..cdb4ed25 100644 --- a/grafana-plugin/src/components/Unauthorized/Unauthorized.tsx +++ b/grafana-plugin/src/components/Unauthorized/Unauthorized.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { VerticalGroup, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'grafana/app/core/core'; @@ -16,9 +16,9 @@ export const Unauthorized: FC = ({ requiredUserAction: { permission, fall const styles = useStyles2(getStyles); return ( -
+
- + 403 diff --git a/grafana-plugin/src/components/UserGroups/UserGroups.tsx b/grafana-plugin/src/components/UserGroups/UserGroups.tsx index a521a738..42373d38 100644 --- a/grafana-plugin/src/components/UserGroups/UserGroups.tsx +++ b/grafana-plugin/src/components/UserGroups/UserGroups.tsx @@ -95,14 +95,14 @@ export const UserGroups = (props: UserGroupsProps) => { }; const renderItem = (item: Item, index: number) => ( -
  • +
  • {renderUser(item.data)} {!disabled && ( -
    +
    @@ -114,7 +114,7 @@ export const UserGroups = (props: UserGroupsProps) => { ); return ( -
    +
    {!disabled && ( { renderItem={renderItem} axis="y" lockAxis="y" - helperClass={cx(styles.sortable)} + helperClass={styles.sortable} items={items} onSortEnd={onSortEnd} handleAddGroup={handleAddUserGroup} @@ -178,7 +178,7 @@ export const SortableList = SortableContainer( }, [items]); return ( -