From 5412f8d4149f7ed0287a334412e30ed2466989d0 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Thu, 2 Mar 2023 13:49:38 +0100 Subject: [PATCH] Alerting: New notification policies view (#61952) --- .betterer.results | 5 +- packages/grafana-data/src/types/icon.ts | 9 + .../src/components/Icon/cached.json | 6 + .../src/components/Icon/iconBundle.ts | 648 ++++++++--------- public/app/features/alerting/routes.tsx | 2 +- .../features/alerting/unified/AmRoutes.tsx | 171 ----- .../features/alerting/unified/MuteTimings.tsx | 2 +- ...test.tsx => NotificationPolicies.test.tsx} | 124 +++- .../alerting/unified/NotificationPolicies.tsx | 341 +++++++++ public/app/features/alerting/unified/TODO.md | 4 + .../alerting/unified/components/HoverCard.tsx | 83 ++- .../alerting/unified/components/Label.tsx | 61 ++ .../alerting/unified/components/MetaText.tsx | 47 ++ .../alerting/unified/components/Strong.tsx | 10 + .../components/amroutes/AmRootRoute.tsx | 89 --- .../components/amroutes/AmRootRouteRead.tsx | 42 -- .../amroutes/AmRoutesExpandedRead.tsx | 121 ---- .../components/amroutes/AmRoutesTable.test.ts | 192 ----- .../components/amroutes/AmRoutesTable.tsx | 273 -------- .../components/amroutes/AmSpecificRouting.tsx | 215 ------ .../unified/components/amroutes/gridStyles.ts | 24 - .../MuteTimingForm.tsx | 0 .../MuteTimingTimeInterval.tsx | 0 .../MuteTimingTimeRange.tsx | 0 .../MuteTimingsTable.tsx | 40 +- .../AlertGroupsSummary.tsx | 41 ++ .../EditDefaultPolicyForm.tsx} | 31 +- .../EditNotificationPolicyForm.tsx} | 85 ++- .../notification-policies/Filters.tsx | 170 +++++ .../notification-policies/Matchers.tsx | 93 +++ .../notification-policies/Modals.tsx | 285 ++++++++ .../notification-policies/Policy.test.tsx | 260 +++++++ .../notification-policies/Policy.tsx | 657 ++++++++++++++++++ .../formStyles.ts | 0 .../app/features/alerting/unified/features.ts | 6 +- .../hooks/useAlertManagerSourceName.ts | 2 +- public/app/features/alerting/unified/mocks.ts | 27 +- .../alerting/unified/state/actions.ts | 1 + .../unified/utils/alertmanager.test.ts | 11 + .../alerting/unified/utils/alertmanager.ts | 10 +- .../alerting/unified/utils/amroutes.test.ts | 41 +- .../alerting/unified/utils/amroutes.ts | 175 +++-- .../features/alerting/unified/utils/misc.ts | 13 + .../utils/notification-policies.test.ts | 119 ++++ .../unified/utils/notification-policies.ts | 105 +++ .../alerting/unified/utils/routeTree.ts | 117 ++++ .../plugins/datasource/alertmanager/types.ts | 7 +- 47 files changed, 3134 insertions(+), 1631 deletions(-) delete mode 100644 public/app/features/alerting/unified/AmRoutes.tsx rename public/app/features/alerting/unified/{AmRoutes.test.tsx => NotificationPolicies.test.tsx} (86%) create mode 100644 public/app/features/alerting/unified/NotificationPolicies.tsx create mode 100644 public/app/features/alerting/unified/components/Label.tsx create mode 100644 public/app/features/alerting/unified/components/MetaText.tsx create mode 100644 public/app/features/alerting/unified/components/Strong.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmRootRouteRead.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedRead.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmRoutesTable.test.ts delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx delete mode 100644 public/app/features/alerting/unified/components/amroutes/gridStyles.ts rename public/app/features/alerting/unified/components/{amroutes => mute-timings}/MuteTimingForm.tsx (100%) rename public/app/features/alerting/unified/components/{amroutes => mute-timings}/MuteTimingTimeInterval.tsx (100%) rename public/app/features/alerting/unified/components/{amroutes => mute-timings}/MuteTimingTimeRange.tsx (100%) rename public/app/features/alerting/unified/components/{amroutes => mute-timings}/MuteTimingsTable.tsx (90%) create mode 100644 public/app/features/alerting/unified/components/notification-policies/AlertGroupsSummary.tsx rename public/app/features/alerting/unified/components/{amroutes/AmRootRouteForm.tsx => notification-policies/EditDefaultPolicyForm.tsx} (92%) rename public/app/features/alerting/unified/components/{amroutes/AmRoutesExpandedForm.tsx => notification-policies/EditNotificationPolicyForm.tsx} (89%) create mode 100644 public/app/features/alerting/unified/components/notification-policies/Filters.tsx create mode 100644 public/app/features/alerting/unified/components/notification-policies/Matchers.tsx create mode 100644 public/app/features/alerting/unified/components/notification-policies/Modals.tsx create mode 100644 public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx create mode 100644 public/app/features/alerting/unified/components/notification-policies/Policy.tsx rename public/app/features/alerting/unified/components/{amroutes => notification-policies}/formStyles.ts (100%) create mode 100644 public/app/features/alerting/unified/utils/notification-policies.test.ts create mode 100644 public/app/features/alerting/unified/utils/notification-policies.ts create mode 100644 public/app/features/alerting/unified/utils/routeTree.ts diff --git a/.betterer.results b/.betterer.results index 97cd79a978d..eadc96c72ad 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1,5 +1,5 @@ // BETTERER RESULTS V2. -// +// // If this file contains merge conflicts, use `betterer merge` to automatically resolve them: // https://phenomnomnominal.github.io/betterer/docs/results-file/#merge // @@ -2631,9 +2631,6 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/alerting/unified/components/receivers/TemplateForm.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 0cf35d13ccd..62bb143eeeb 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -4,6 +4,11 @@ export const availableIconsIndex = { github: true, gitlab: true, okta: true, + discord: true, + hipchat: true, + 'google-hangouts-alt': true, + pagerduty: true, + line: true, anchor: true, 'adjust-circle': true, 'angle-double-down': true, @@ -24,6 +29,7 @@ export const availableIconsIndex = { 'arrow-up': true, 'arrows-h': true, 'arrows-v': true, + at: true, backward: true, bars: true, bell: true, @@ -37,6 +43,7 @@ export const availableIconsIndex = { building: true, 'calculator-alt': true, 'calendar-alt': true, + 'calendar-slash': true, camera: true, capture: true, 'channel-add': true, @@ -60,6 +67,7 @@ export const availableIconsIndex = { 'comments-alt': true, compass: true, copy: true, + 'corner-down-right-alt': true, 'create-dashboard': true, 'credit-card': true, crosshair: true, @@ -133,6 +141,7 @@ export const availableIconsIndex = { 'key-skeleton-alt': true, keyboard: true, 'layer-group': true, + 'layers-alt': true, 'library-panel': true, 'line-alt': true, link: true, diff --git a/packages/grafana-ui/src/components/Icon/cached.json b/packages/grafana-ui/src/components/Icon/cached.json index 401d8c5f8b6..24718642689 100644 --- a/packages/grafana-ui/src/components/Icon/cached.json +++ b/packages/grafana-ui/src/components/Icon/cached.json @@ -1,4 +1,5 @@ [ + "unicons/at", "unicons/adjust-circle", "unicons/angle-double-down", "unicons/angle-double-right", @@ -13,6 +14,7 @@ "unicons/arrow-left", "unicons/arrow-random", "unicons/arrow-right", + "unicons/arrow-to-right", "unicons/arrow-up", "unicons/arrows-h", "unicons/backward", @@ -27,6 +29,7 @@ "unicons/building", "unicons/calculator-alt", "unicons/calendar-alt", + "unicons/calendar-slash", "unicons/camera", "unicons/channel-add", "unicons/chart-line", @@ -45,6 +48,7 @@ "unicons/comments-alt", "unicons/compass", "unicons/copy", + "unicons/corner-down-right-alt", "unicons/cube", "unicons/dashboard", "unicons/database", @@ -95,6 +99,7 @@ "unicons/question-circle", "unicons/repeat", "unicons/rocket", + "unicons/rss", "unicons/save", "unicons/search", "unicons/search-minus", @@ -128,6 +133,7 @@ "unicons/fire", "unicons/hourglass", "unicons/layer-group", + "unicons/layers-alt", "unicons/line-alt", "unicons/list-ui-alt", "unicons/message", diff --git a/packages/grafana-ui/src/components/Icon/iconBundle.ts b/packages/grafana-ui/src/components/Icon/iconBundle.ts index 27d94baa194..7e1bc30a5a4 100644 --- a/packages/grafana-ui/src/components/Icon/iconBundle.ts +++ b/packages/grafana-ui/src/components/Icon/iconBundle.ts @@ -7,165 +7,171 @@ import { cacheStore } from 'react-inlinesvg'; // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json -import u1000 from '../../../../../public/img/icons/unicons/adjust-circle.svg'; -import u1001 from '../../../../../public/img/icons/unicons/angle-double-down.svg'; -import u1002 from '../../../../../public/img/icons/unicons/angle-double-right.svg'; -import u1003 from '../../../../../public/img/icons/unicons/angle-down.svg'; -import u1004 from '../../../../../public/img/icons/unicons/angle-left.svg'; -import u1005 from '../../../../../public/img/icons/unicons/angle-right.svg'; -import u1006 from '../../../../../public/img/icons/unicons/angle-up.svg'; -import u1007 from '../../../../../public/img/icons/unicons/apps.svg'; -import u1008 from '../../../../../public/img/icons/unicons/arrow.svg'; -import u1009 from '../../../../../public/img/icons/unicons/arrow-down.svg'; -import u1010 from '../../../../../public/img/icons/unicons/arrow-from-right.svg'; -import u1011 from '../../../../../public/img/icons/unicons/arrow-left.svg'; -import u1012 from '../../../../../public/img/icons/unicons/arrow-random.svg'; -import u1013 from '../../../../../public/img/icons/unicons/arrow-right.svg'; -import u1014 from '../../../../../public/img/icons/unicons/arrow-up.svg'; -import u1015 from '../../../../../public/img/icons/unicons/arrows-h.svg'; -import u1016 from '../../../../../public/img/icons/unicons/backward.svg'; -import u1017 from '../../../../../public/img/icons/unicons/bars.svg'; -import u1018 from '../../../../../public/img/icons/unicons/bell.svg'; -import u1019 from '../../../../../public/img/icons/unicons/bell-slash.svg'; -import u1020 from '../../../../../public/img/icons/unicons/bolt.svg'; -import u1021 from '../../../../../public/img/icons/unicons/book.svg'; -import u1022 from '../../../../../public/img/icons/unicons/book-open.svg'; -import u1023 from '../../../../../public/img/icons/unicons/brackets-curly.svg'; -import u1024 from '../../../../../public/img/icons/unicons/bug.svg'; -import u1025 from '../../../../../public/img/icons/unicons/building.svg'; -import u1026 from '../../../../../public/img/icons/unicons/calculator-alt.svg'; -import u1027 from '../../../../../public/img/icons/unicons/calendar-alt.svg'; -import u1028 from '../../../../../public/img/icons/unicons/camera.svg'; -import u1029 from '../../../../../public/img/icons/unicons/channel-add.svg'; -import u1030 from '../../../../../public/img/icons/unicons/chart-line.svg'; -import u1031 from '../../../../../public/img/icons/unicons/check.svg'; -import u1032 from '../../../../../public/img/icons/unicons/check-circle.svg'; -import u1033 from '../../../../../public/img/icons/unicons/circle.svg'; -import u1034 from '../../../../../public/img/icons/unicons/clipboard-alt.svg'; -import u1035 from '../../../../../public/img/icons/unicons/clock-nine.svg'; -import u1036 from '../../../../../public/img/icons/unicons/cloud.svg'; -import u1037 from '../../../../../public/img/icons/unicons/cloud-download.svg'; -import u1038 from '../../../../../public/img/icons/unicons/code-branch.svg'; -import u1039 from '../../../../../public/img/icons/unicons/cog.svg'; -import u1040 from '../../../../../public/img/icons/unicons/columns.svg'; -import u1041 from '../../../../../public/img/icons/unicons/comment-alt.svg'; -import u1042 from '../../../../../public/img/icons/unicons/comment-alt-share.svg'; -import u1043 from '../../../../../public/img/icons/unicons/comments-alt.svg'; -import u1044 from '../../../../../public/img/icons/unicons/compass.svg'; -import u1045 from '../../../../../public/img/icons/unicons/copy.svg'; -import u1046 from '../../../../../public/img/icons/unicons/cube.svg'; -import u1047 from '../../../../../public/img/icons/unicons/dashboard.svg'; -import u1048 from '../../../../../public/img/icons/unicons/database.svg'; -import u1049 from '../../../../../public/img/icons/unicons/document-info.svg'; -import u1050 from '../../../../../public/img/icons/unicons/download-alt.svg'; -import u1051 from '../../../../../public/img/icons/unicons/draggabledots.svg'; -import u1052 from '../../../../../public/img/icons/unicons/edit.svg'; -import u1053 from '../../../../../public/img/icons/unicons/ellipsis-v.svg'; -import u1054 from '../../../../../public/img/icons/unicons/envelope.svg'; -import u1055 from '../../../../../public/img/icons/unicons/exchange-alt.svg'; -import u1056 from '../../../../../public/img/icons/unicons/exclamation-triangle.svg'; -import u1057 from '../../../../../public/img/icons/unicons/external-link-alt.svg'; -import u1058 from '../../../../../public/img/icons/unicons/eye.svg'; -import u1059 from '../../../../../public/img/icons/unicons/eye-slash.svg'; -import u1060 from '../../../../../public/img/icons/unicons/file-alt.svg'; -import u1061 from '../../../../../public/img/icons/unicons/file-blank.svg'; -import u1062 from '../../../../../public/img/icons/unicons/filter.svg'; -import u1063 from '../../../../../public/img/icons/unicons/folder.svg'; -import u1064 from '../../../../../public/img/icons/unicons/folder-open.svg'; -import u1065 from '../../../../../public/img/icons/unicons/folder-plus.svg'; -import u1066 from '../../../../../public/img/icons/unicons/folder-upload.svg'; -import u1067 from '../../../../../public/img/icons/unicons/forward.svg'; -import u1068 from '../../../../../public/img/icons/unicons/graph-bar.svg'; -import u1069 from '../../../../../public/img/icons/unicons/history.svg'; -import u1070 from '../../../../../public/img/icons/unicons/home-alt.svg'; -import u1071 from '../../../../../public/img/icons/unicons/import.svg'; -import u1072 from '../../../../../public/img/icons/unicons/info.svg'; -import u1073 from '../../../../../public/img/icons/unicons/info-circle.svg'; -import u1074 from '../../../../../public/img/icons/unicons/k6.svg'; -import u1075 from '../../../../../public/img/icons/unicons/key-skeleton-alt.svg'; -import u1076 from '../../../../../public/img/icons/unicons/keyboard.svg'; -import u1077 from '../../../../../public/img/icons/unicons/link.svg'; -import u1078 from '../../../../../public/img/icons/unicons/list-ul.svg'; -import u1079 from '../../../../../public/img/icons/unicons/lock.svg'; -import u1080 from '../../../../../public/img/icons/unicons/minus.svg'; -import u1081 from '../../../../../public/img/icons/unicons/minus-circle.svg'; -import u1082 from '../../../../../public/img/icons/unicons/mobile-android.svg'; -import u1083 from '../../../../../public/img/icons/unicons/monitor.svg'; -import u1084 from '../../../../../public/img/icons/unicons/pause.svg'; -import u1085 from '../../../../../public/img/icons/unicons/pen.svg'; -import u1086 from '../../../../../public/img/icons/unicons/play.svg'; -import u1087 from '../../../../../public/img/icons/unicons/plug.svg'; -import u1088 from '../../../../../public/img/icons/unicons/plus.svg'; -import u1089 from '../../../../../public/img/icons/unicons/plus-circle.svg'; -import u1090 from '../../../../../public/img/icons/unicons/power.svg'; -import u1091 from '../../../../../public/img/icons/unicons/presentation-play.svg'; -import u1092 from '../../../../../public/img/icons/unicons/process.svg'; -import u1093 from '../../../../../public/img/icons/unicons/question-circle.svg'; -import u1094 from '../../../../../public/img/icons/unicons/repeat.svg'; -import u1095 from '../../../../../public/img/icons/unicons/rocket.svg'; -import u1096 from '../../../../../public/img/icons/unicons/save.svg'; -import u1097 from '../../../../../public/img/icons/unicons/search.svg'; -import u1098 from '../../../../../public/img/icons/unicons/search-minus.svg'; -import u1099 from '../../../../../public/img/icons/unicons/search-plus.svg'; -import u1100 from '../../../../../public/img/icons/unicons/share-alt.svg'; -import u1101 from '../../../../../public/img/icons/unicons/shield.svg'; -import u1102 from '../../../../../public/img/icons/unicons/signal.svg'; -import u1103 from '../../../../../public/img/icons/unicons/signin.svg'; -import u1104 from '../../../../../public/img/icons/unicons/signout.svg'; -import u1105 from '../../../../../public/img/icons/unicons/sitemap.svg'; -import u1106 from '../../../../../public/img/icons/unicons/slack.svg'; -import u1107 from '../../../../../public/img/icons/unicons/sliders-v-alt.svg'; -import u1108 from '../../../../../public/img/icons/unicons/sort-amount-down.svg'; -import u1109 from '../../../../../public/img/icons/unicons/sort-amount-up.svg'; -import u1110 from '../../../../../public/img/icons/unicons/square-shape.svg'; -import u1111 from '../../../../../public/img/icons/unicons/star.svg'; -import u1112 from '../../../../../public/img/icons/unicons/step-backward.svg'; -import u1113 from '../../../../../public/img/icons/unicons/sync.svg'; -import u1114 from '../../../../../public/img/icons/unicons/table.svg'; -import u1115 from '../../../../../public/img/icons/unicons/tag-alt.svg'; -import u1116 from '../../../../../public/img/icons/unicons/times.svg'; -import u1117 from '../../../../../public/img/icons/unicons/trash-alt.svg'; -import u1118 from '../../../../../public/img/icons/unicons/unlock.svg'; -import u1119 from '../../../../../public/img/icons/unicons/upload.svg'; -import u1120 from '../../../../../public/img/icons/unicons/user.svg'; -import u1121 from '../../../../../public/img/icons/unicons/users-alt.svg'; -import u1122 from '../../../../../public/img/icons/unicons/wrap-text.svg'; -import u1123 from '../../../../../public/img/icons/unicons/cloud-upload.svg'; -import u1124 from '../../../../../public/img/icons/unicons/credit-card.svg'; -import u1125 from '../../../../../public/img/icons/unicons/file-copy-alt.svg'; -import u1126 from '../../../../../public/img/icons/unicons/fire.svg'; -import u1127 from '../../../../../public/img/icons/unicons/hourglass.svg'; -import u1128 from '../../../../../public/img/icons/unicons/layer-group.svg'; -import u1129 from '../../../../../public/img/icons/unicons/line-alt.svg'; -import u1130 from '../../../../../public/img/icons/unicons/list-ui-alt.svg'; -import u1131 from '../../../../../public/img/icons/unicons/message.svg'; -import u1132 from '../../../../../public/img/icons/unicons/palette.svg'; -import u1133 from '../../../../../public/img/icons/unicons/percentage.svg'; -import u1134 from '../../../../../public/img/icons/unicons/shield-exclamation.svg'; -import u1135 from '../../../../../public/img/icons/unicons/plus-square.svg'; -import u1136 from '../../../../../public/img/icons/unicons/x.svg'; -import u1137 from '../../../../../public/img/icons/unicons/capture.svg'; -import u1138 from '../../../../../public/img/icons/custom/gf-grid.svg'; -import u1139 from '../../../../../public/img/icons/custom/gf-landscape.svg'; -import u1140 from '../../../../../public/img/icons/custom/gf-layout-simple.svg'; -import u1141 from '../../../../../public/img/icons/custom/gf-portrait.svg'; -import u1142 from '../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; -import u1143 from '../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; -import u1144 from '../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; -import u1145 from '../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; -import u1146 from '../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; -import u1147 from '../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; -import u1148 from '../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; -import u1149 from '../../../../../public/img/icons/custom/gf-logs.svg'; -import u1150 from '../../../../../public/img/icons/custom/gf-movepane-left.svg'; -import u1151 from '../../../../../public/img/icons/custom/gf-movepane-right.svg'; -import u1152 from '../../../../../public/img/icons/mono/favorite.svg'; -import u1153 from '../../../../../public/img/icons/mono/grafana.svg'; -import u1154 from '../../../../../public/img/icons/mono/heart.svg'; -import u1155 from '../../../../../public/img/icons/mono/heart-break.svg'; -import u1156 from '../../../../../public/img/icons/mono/panel-add.svg'; -import u1157 from '../../../../../public/img/icons/mono/library-panel.svg'; -import u1158 from '../../../../../public/img/icons/unicons/record-audio.svg'; +import u1000 from '../../../../../public/img/icons/unicons/at.svg'; +import u1001 from '../../../../../public/img/icons/unicons/adjust-circle.svg'; +import u1002 from '../../../../../public/img/icons/unicons/angle-double-down.svg'; +import u1003 from '../../../../../public/img/icons/unicons/angle-double-right.svg'; +import u1004 from '../../../../../public/img/icons/unicons/angle-down.svg'; +import u1005 from '../../../../../public/img/icons/unicons/angle-left.svg'; +import u1006 from '../../../../../public/img/icons/unicons/angle-right.svg'; +import u1007 from '../../../../../public/img/icons/unicons/angle-up.svg'; +import u1008 from '../../../../../public/img/icons/unicons/apps.svg'; +import u1009 from '../../../../../public/img/icons/unicons/arrow.svg'; +import u1010 from '../../../../../public/img/icons/unicons/arrow-down.svg'; +import u1011 from '../../../../../public/img/icons/unicons/arrow-from-right.svg'; +import u1012 from '../../../../../public/img/icons/unicons/arrow-left.svg'; +import u1013 from '../../../../../public/img/icons/unicons/arrow-random.svg'; +import u1014 from '../../../../../public/img/icons/unicons/arrow-right.svg'; +import u1015 from '../../../../../public/img/icons/unicons/arrow-to-right.svg'; +import u1016 from '../../../../../public/img/icons/unicons/arrow-up.svg'; +import u1017 from '../../../../../public/img/icons/unicons/arrows-h.svg'; +import u1018 from '../../../../../public/img/icons/unicons/backward.svg'; +import u1019 from '../../../../../public/img/icons/unicons/bars.svg'; +import u1020 from '../../../../../public/img/icons/unicons/bell.svg'; +import u1021 from '../../../../../public/img/icons/unicons/bell-slash.svg'; +import u1022 from '../../../../../public/img/icons/unicons/bolt.svg'; +import u1023 from '../../../../../public/img/icons/unicons/book.svg'; +import u1024 from '../../../../../public/img/icons/unicons/book-open.svg'; +import u1025 from '../../../../../public/img/icons/unicons/brackets-curly.svg'; +import u1026 from '../../../../../public/img/icons/unicons/bug.svg'; +import u1027 from '../../../../../public/img/icons/unicons/building.svg'; +import u1028 from '../../../../../public/img/icons/unicons/calculator-alt.svg'; +import u1029 from '../../../../../public/img/icons/unicons/calendar-alt.svg'; +import u1030 from '../../../../../public/img/icons/unicons/calendar-slash.svg'; +import u1031 from '../../../../../public/img/icons/unicons/camera.svg'; +import u1032 from '../../../../../public/img/icons/unicons/channel-add.svg'; +import u1033 from '../../../../../public/img/icons/unicons/chart-line.svg'; +import u1034 from '../../../../../public/img/icons/unicons/check.svg'; +import u1035 from '../../../../../public/img/icons/unicons/check-circle.svg'; +import u1036 from '../../../../../public/img/icons/unicons/circle.svg'; +import u1037 from '../../../../../public/img/icons/unicons/clipboard-alt.svg'; +import u1038 from '../../../../../public/img/icons/unicons/clock-nine.svg'; +import u1039 from '../../../../../public/img/icons/unicons/cloud.svg'; +import u1040 from '../../../../../public/img/icons/unicons/cloud-download.svg'; +import u1041 from '../../../../../public/img/icons/unicons/code-branch.svg'; +import u1042 from '../../../../../public/img/icons/unicons/cog.svg'; +import u1043 from '../../../../../public/img/icons/unicons/columns.svg'; +import u1044 from '../../../../../public/img/icons/unicons/comment-alt.svg'; +import u1045 from '../../../../../public/img/icons/unicons/comment-alt-share.svg'; +import u1046 from '../../../../../public/img/icons/unicons/comments-alt.svg'; +import u1047 from '../../../../../public/img/icons/unicons/compass.svg'; +import u1048 from '../../../../../public/img/icons/unicons/copy.svg'; +import u1049 from '../../../../../public/img/icons/unicons/corner-down-right-alt.svg'; +import u1050 from '../../../../../public/img/icons/unicons/cube.svg'; +import u1051 from '../../../../../public/img/icons/unicons/dashboard.svg'; +import u1052 from '../../../../../public/img/icons/unicons/database.svg'; +import u1053 from '../../../../../public/img/icons/unicons/document-info.svg'; +import u1054 from '../../../../../public/img/icons/unicons/download-alt.svg'; +import u1055 from '../../../../../public/img/icons/unicons/draggabledots.svg'; +import u1056 from '../../../../../public/img/icons/unicons/edit.svg'; +import u1057 from '../../../../../public/img/icons/unicons/ellipsis-v.svg'; +import u1058 from '../../../../../public/img/icons/unicons/envelope.svg'; +import u1059 from '../../../../../public/img/icons/unicons/exchange-alt.svg'; +import u1060 from '../../../../../public/img/icons/unicons/exclamation-triangle.svg'; +import u1061 from '../../../../../public/img/icons/unicons/external-link-alt.svg'; +import u1062 from '../../../../../public/img/icons/unicons/eye.svg'; +import u1063 from '../../../../../public/img/icons/unicons/eye-slash.svg'; +import u1064 from '../../../../../public/img/icons/unicons/file-alt.svg'; +import u1065 from '../../../../../public/img/icons/unicons/file-blank.svg'; +import u1066 from '../../../../../public/img/icons/unicons/filter.svg'; +import u1067 from '../../../../../public/img/icons/unicons/folder.svg'; +import u1068 from '../../../../../public/img/icons/unicons/folder-open.svg'; +import u1069 from '../../../../../public/img/icons/unicons/folder-plus.svg'; +import u1070 from '../../../../../public/img/icons/unicons/folder-upload.svg'; +import u1071 from '../../../../../public/img/icons/unicons/forward.svg'; +import u1072 from '../../../../../public/img/icons/unicons/graph-bar.svg'; +import u1073 from '../../../../../public/img/icons/unicons/history.svg'; +import u1074 from '../../../../../public/img/icons/unicons/home-alt.svg'; +import u1075 from '../../../../../public/img/icons/unicons/import.svg'; +import u1076 from '../../../../../public/img/icons/unicons/info.svg'; +import u1077 from '../../../../../public/img/icons/unicons/info-circle.svg'; +import u1078 from '../../../../../public/img/icons/unicons/k6.svg'; +import u1079 from '../../../../../public/img/icons/unicons/key-skeleton-alt.svg'; +import u1080 from '../../../../../public/img/icons/unicons/keyboard.svg'; +import u1081 from '../../../../../public/img/icons/unicons/link.svg'; +import u1082 from '../../../../../public/img/icons/unicons/list-ul.svg'; +import u1083 from '../../../../../public/img/icons/unicons/lock.svg'; +import u1084 from '../../../../../public/img/icons/unicons/minus.svg'; +import u1085 from '../../../../../public/img/icons/unicons/minus-circle.svg'; +import u1086 from '../../../../../public/img/icons/unicons/mobile-android.svg'; +import u1087 from '../../../../../public/img/icons/unicons/monitor.svg'; +import u1088 from '../../../../../public/img/icons/unicons/pause.svg'; +import u1089 from '../../../../../public/img/icons/unicons/pen.svg'; +import u1090 from '../../../../../public/img/icons/unicons/play.svg'; +import u1091 from '../../../../../public/img/icons/unicons/plug.svg'; +import u1092 from '../../../../../public/img/icons/unicons/plus.svg'; +import u1093 from '../../../../../public/img/icons/unicons/plus-circle.svg'; +import u1094 from '../../../../../public/img/icons/unicons/power.svg'; +import u1095 from '../../../../../public/img/icons/unicons/presentation-play.svg'; +import u1096 from '../../../../../public/img/icons/unicons/process.svg'; +import u1097 from '../../../../../public/img/icons/unicons/question-circle.svg'; +import u1098 from '../../../../../public/img/icons/unicons/repeat.svg'; +import u1099 from '../../../../../public/img/icons/unicons/rocket.svg'; +import u1100 from '../../../../../public/img/icons/unicons/rss.svg'; +import u1101 from '../../../../../public/img/icons/unicons/save.svg'; +import u1102 from '../../../../../public/img/icons/unicons/search.svg'; +import u1103 from '../../../../../public/img/icons/unicons/search-minus.svg'; +import u1104 from '../../../../../public/img/icons/unicons/search-plus.svg'; +import u1105 from '../../../../../public/img/icons/unicons/share-alt.svg'; +import u1106 from '../../../../../public/img/icons/unicons/shield.svg'; +import u1107 from '../../../../../public/img/icons/unicons/signal.svg'; +import u1108 from '../../../../../public/img/icons/unicons/signin.svg'; +import u1109 from '../../../../../public/img/icons/unicons/signout.svg'; +import u1110 from '../../../../../public/img/icons/unicons/sitemap.svg'; +import u1111 from '../../../../../public/img/icons/unicons/slack.svg'; +import u1112 from '../../../../../public/img/icons/unicons/sliders-v-alt.svg'; +import u1113 from '../../../../../public/img/icons/unicons/sort-amount-down.svg'; +import u1114 from '../../../../../public/img/icons/unicons/sort-amount-up.svg'; +import u1115 from '../../../../../public/img/icons/unicons/square-shape.svg'; +import u1116 from '../../../../../public/img/icons/unicons/star.svg'; +import u1117 from '../../../../../public/img/icons/unicons/step-backward.svg'; +import u1118 from '../../../../../public/img/icons/unicons/sync.svg'; +import u1119 from '../../../../../public/img/icons/unicons/table.svg'; +import u1120 from '../../../../../public/img/icons/unicons/tag-alt.svg'; +import u1121 from '../../../../../public/img/icons/unicons/times.svg'; +import u1122 from '../../../../../public/img/icons/unicons/trash-alt.svg'; +import u1123 from '../../../../../public/img/icons/unicons/unlock.svg'; +import u1124 from '../../../../../public/img/icons/unicons/upload.svg'; +import u1125 from '../../../../../public/img/icons/unicons/user.svg'; +import u1126 from '../../../../../public/img/icons/unicons/users-alt.svg'; +import u1127 from '../../../../../public/img/icons/unicons/wrap-text.svg'; +import u1128 from '../../../../../public/img/icons/unicons/cloud-upload.svg'; +import u1129 from '../../../../../public/img/icons/unicons/credit-card.svg'; +import u1130 from '../../../../../public/img/icons/unicons/file-copy-alt.svg'; +import u1131 from '../../../../../public/img/icons/unicons/fire.svg'; +import u1132 from '../../../../../public/img/icons/unicons/hourglass.svg'; +import u1133 from '../../../../../public/img/icons/unicons/layer-group.svg'; +import u1134 from '../../../../../public/img/icons/unicons/layers-alt.svg'; +import u1135 from '../../../../../public/img/icons/unicons/line-alt.svg'; +import u1136 from '../../../../../public/img/icons/unicons/list-ui-alt.svg'; +import u1137 from '../../../../../public/img/icons/unicons/message.svg'; +import u1138 from '../../../../../public/img/icons/unicons/palette.svg'; +import u1139 from '../../../../../public/img/icons/unicons/percentage.svg'; +import u1140 from '../../../../../public/img/icons/unicons/shield-exclamation.svg'; +import u1141 from '../../../../../public/img/icons/unicons/plus-square.svg'; +import u1142 from '../../../../../public/img/icons/unicons/x.svg'; +import u1143 from '../../../../../public/img/icons/unicons/capture.svg'; +import u1144 from '../../../../../public/img/icons/custom/gf-grid.svg'; +import u1145 from '../../../../../public/img/icons/custom/gf-landscape.svg'; +import u1146 from '../../../../../public/img/icons/custom/gf-layout-simple.svg'; +import u1147 from '../../../../../public/img/icons/custom/gf-portrait.svg'; +import u1148 from '../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; +import u1149 from '../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; +import u1150 from '../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; +import u1151 from '../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; +import u1152 from '../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; +import u1153 from '../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; +import u1154 from '../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; +import u1155 from '../../../../../public/img/icons/custom/gf-logs.svg'; +import u1156 from '../../../../../public/img/icons/custom/gf-movepane-left.svg'; +import u1157 from '../../../../../public/img/icons/custom/gf-movepane-right.svg'; +import u1158 from '../../../../../public/img/icons/mono/favorite.svg'; +import u1159 from '../../../../../public/img/icons/mono/grafana.svg'; +import u1160 from '../../../../../public/img/icons/mono/heart.svg'; +import u1161 from '../../../../../public/img/icons/mono/heart-break.svg'; +import u1162 from '../../../../../public/img/icons/mono/panel-add.svg'; +import u1163 from '../../../../../public/img/icons/mono/library-panel.svg'; +import u1164 from '../../../../../public/img/icons/unicons/record-audio.svg'; // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json @@ -188,165 +194,171 @@ export function initIconCache() { // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json - cacheItem(u1000, 'unicons/adjust-circle.svg'); - cacheItem(u1001, 'unicons/angle-double-down.svg'); - cacheItem(u1002, 'unicons/angle-double-right.svg'); - cacheItem(u1003, 'unicons/angle-down.svg'); - cacheItem(u1004, 'unicons/angle-left.svg'); - cacheItem(u1005, 'unicons/angle-right.svg'); - cacheItem(u1006, 'unicons/angle-up.svg'); - cacheItem(u1007, 'unicons/apps.svg'); - cacheItem(u1008, 'unicons/arrow.svg'); - cacheItem(u1009, 'unicons/arrow-down.svg'); - cacheItem(u1010, 'unicons/arrow-from-right.svg'); - cacheItem(u1011, 'unicons/arrow-left.svg'); - cacheItem(u1012, 'unicons/arrow-random.svg'); - cacheItem(u1013, 'unicons/arrow-right.svg'); - cacheItem(u1014, 'unicons/arrow-up.svg'); - cacheItem(u1015, 'unicons/arrows-h.svg'); - cacheItem(u1016, 'unicons/backward.svg'); - cacheItem(u1017, 'unicons/bars.svg'); - cacheItem(u1018, 'unicons/bell.svg'); - cacheItem(u1019, 'unicons/bell-slash.svg'); - cacheItem(u1020, 'unicons/bolt.svg'); - cacheItem(u1021, 'unicons/book.svg'); - cacheItem(u1022, 'unicons/book-open.svg'); - cacheItem(u1023, 'unicons/brackets-curly.svg'); - cacheItem(u1024, 'unicons/bug.svg'); - cacheItem(u1025, 'unicons/building.svg'); - cacheItem(u1026, 'unicons/calculator-alt.svg'); - cacheItem(u1027, 'unicons/calendar-alt.svg'); - cacheItem(u1028, 'unicons/camera.svg'); - cacheItem(u1029, 'unicons/channel-add.svg'); - cacheItem(u1030, 'unicons/chart-line.svg'); - cacheItem(u1031, 'unicons/check.svg'); - cacheItem(u1032, 'unicons/check-circle.svg'); - cacheItem(u1033, 'unicons/circle.svg'); - cacheItem(u1034, 'unicons/clipboard-alt.svg'); - cacheItem(u1035, 'unicons/clock-nine.svg'); - cacheItem(u1036, 'unicons/cloud.svg'); - cacheItem(u1037, 'unicons/cloud-download.svg'); - cacheItem(u1038, 'unicons/code-branch.svg'); - cacheItem(u1039, 'unicons/cog.svg'); - cacheItem(u1040, 'unicons/columns.svg'); - cacheItem(u1041, 'unicons/comment-alt.svg'); - cacheItem(u1042, 'unicons/comment-alt-share.svg'); - cacheItem(u1043, 'unicons/comments-alt.svg'); - cacheItem(u1044, 'unicons/compass.svg'); - cacheItem(u1045, 'unicons/copy.svg'); - cacheItem(u1046, 'unicons/cube.svg'); - cacheItem(u1047, 'unicons/dashboard.svg'); - cacheItem(u1048, 'unicons/database.svg'); - cacheItem(u1049, 'unicons/document-info.svg'); - cacheItem(u1050, 'unicons/download-alt.svg'); - cacheItem(u1051, 'unicons/draggabledots.svg'); - cacheItem(u1052, 'unicons/edit.svg'); - cacheItem(u1053, 'unicons/ellipsis-v.svg'); - cacheItem(u1054, 'unicons/envelope.svg'); - cacheItem(u1055, 'unicons/exchange-alt.svg'); - cacheItem(u1056, 'unicons/exclamation-triangle.svg'); - cacheItem(u1057, 'unicons/external-link-alt.svg'); - cacheItem(u1058, 'unicons/eye.svg'); - cacheItem(u1059, 'unicons/eye-slash.svg'); - cacheItem(u1060, 'unicons/file-alt.svg'); - cacheItem(u1061, 'unicons/file-blank.svg'); - cacheItem(u1062, 'unicons/filter.svg'); - cacheItem(u1063, 'unicons/folder.svg'); - cacheItem(u1064, 'unicons/folder-open.svg'); - cacheItem(u1065, 'unicons/folder-plus.svg'); - cacheItem(u1066, 'unicons/folder-upload.svg'); - cacheItem(u1067, 'unicons/forward.svg'); - cacheItem(u1068, 'unicons/graph-bar.svg'); - cacheItem(u1069, 'unicons/history.svg'); - cacheItem(u1070, 'unicons/home-alt.svg'); - cacheItem(u1071, 'unicons/import.svg'); - cacheItem(u1072, 'unicons/info.svg'); - cacheItem(u1073, 'unicons/info-circle.svg'); - cacheItem(u1074, 'unicons/k6.svg'); - cacheItem(u1075, 'unicons/key-skeleton-alt.svg'); - cacheItem(u1076, 'unicons/keyboard.svg'); - cacheItem(u1077, 'unicons/link.svg'); - cacheItem(u1078, 'unicons/list-ul.svg'); - cacheItem(u1079, 'unicons/lock.svg'); - cacheItem(u1080, 'unicons/minus.svg'); - cacheItem(u1081, 'unicons/minus-circle.svg'); - cacheItem(u1082, 'unicons/mobile-android.svg'); - cacheItem(u1083, 'unicons/monitor.svg'); - cacheItem(u1084, 'unicons/pause.svg'); - cacheItem(u1085, 'unicons/pen.svg'); - cacheItem(u1086, 'unicons/play.svg'); - cacheItem(u1087, 'unicons/plug.svg'); - cacheItem(u1088, 'unicons/plus.svg'); - cacheItem(u1089, 'unicons/plus-circle.svg'); - cacheItem(u1090, 'unicons/power.svg'); - cacheItem(u1091, 'unicons/presentation-play.svg'); - cacheItem(u1092, 'unicons/process.svg'); - cacheItem(u1093, 'unicons/question-circle.svg'); - cacheItem(u1094, 'unicons/repeat.svg'); - cacheItem(u1095, 'unicons/rocket.svg'); - cacheItem(u1096, 'unicons/save.svg'); - cacheItem(u1097, 'unicons/search.svg'); - cacheItem(u1098, 'unicons/search-minus.svg'); - cacheItem(u1099, 'unicons/search-plus.svg'); - cacheItem(u1100, 'unicons/share-alt.svg'); - cacheItem(u1101, 'unicons/shield.svg'); - cacheItem(u1102, 'unicons/signal.svg'); - cacheItem(u1103, 'unicons/signin.svg'); - cacheItem(u1104, 'unicons/signout.svg'); - cacheItem(u1105, 'unicons/sitemap.svg'); - cacheItem(u1106, 'unicons/slack.svg'); - cacheItem(u1107, 'unicons/sliders-v-alt.svg'); - cacheItem(u1108, 'unicons/sort-amount-down.svg'); - cacheItem(u1109, 'unicons/sort-amount-up.svg'); - cacheItem(u1110, 'unicons/square-shape.svg'); - cacheItem(u1111, 'unicons/star.svg'); - cacheItem(u1112, 'unicons/step-backward.svg'); - cacheItem(u1113, 'unicons/sync.svg'); - cacheItem(u1114, 'unicons/table.svg'); - cacheItem(u1115, 'unicons/tag-alt.svg'); - cacheItem(u1116, 'unicons/times.svg'); - cacheItem(u1117, 'unicons/trash-alt.svg'); - cacheItem(u1118, 'unicons/unlock.svg'); - cacheItem(u1119, 'unicons/upload.svg'); - cacheItem(u1120, 'unicons/user.svg'); - cacheItem(u1121, 'unicons/users-alt.svg'); - cacheItem(u1122, 'unicons/wrap-text.svg'); - cacheItem(u1123, 'unicons/cloud-upload.svg'); - cacheItem(u1124, 'unicons/credit-card.svg'); - cacheItem(u1125, 'unicons/file-copy-alt.svg'); - cacheItem(u1126, 'unicons/fire.svg'); - cacheItem(u1127, 'unicons/hourglass.svg'); - cacheItem(u1128, 'unicons/layer-group.svg'); - cacheItem(u1129, 'unicons/line-alt.svg'); - cacheItem(u1130, 'unicons/list-ui-alt.svg'); - cacheItem(u1131, 'unicons/message.svg'); - cacheItem(u1132, 'unicons/palette.svg'); - cacheItem(u1133, 'unicons/percentage.svg'); - cacheItem(u1134, 'unicons/shield-exclamation.svg'); - cacheItem(u1135, 'unicons/plus-square.svg'); - cacheItem(u1136, 'unicons/x.svg'); - cacheItem(u1137, 'unicons/capture.svg'); - cacheItem(u1138, 'custom/gf-grid.svg'); - cacheItem(u1139, 'custom/gf-landscape.svg'); - cacheItem(u1140, 'custom/gf-layout-simple.svg'); - cacheItem(u1141, 'custom/gf-portrait.svg'); - cacheItem(u1142, 'custom/gf-bar-alignment-after.svg'); - cacheItem(u1143, 'custom/gf-bar-alignment-before.svg'); - cacheItem(u1144, 'custom/gf-bar-alignment-center.svg'); - cacheItem(u1145, 'custom/gf-interpolation-linear.svg'); - cacheItem(u1146, 'custom/gf-interpolation-smooth.svg'); - cacheItem(u1147, 'custom/gf-interpolation-step-after.svg'); - cacheItem(u1148, 'custom/gf-interpolation-step-before.svg'); - cacheItem(u1149, 'custom/gf-logs.svg'); - cacheItem(u1150, 'custom/gf-movepane-left.svg'); - cacheItem(u1151, 'custom/gf-movepane-right.svg'); - cacheItem(u1152, 'mono/favorite.svg'); - cacheItem(u1153, 'mono/grafana.svg'); - cacheItem(u1154, 'mono/heart.svg'); - cacheItem(u1155, 'mono/heart-break.svg'); - cacheItem(u1156, 'mono/panel-add.svg'); - cacheItem(u1157, 'mono/library-panel.svg'); - cacheItem(u1158, 'unicons/record-audio.svg'); + cacheItem(u1000, 'unicons/at.svg'); + cacheItem(u1001, 'unicons/adjust-circle.svg'); + cacheItem(u1002, 'unicons/angle-double-down.svg'); + cacheItem(u1003, 'unicons/angle-double-right.svg'); + cacheItem(u1004, 'unicons/angle-down.svg'); + cacheItem(u1005, 'unicons/angle-left.svg'); + cacheItem(u1006, 'unicons/angle-right.svg'); + cacheItem(u1007, 'unicons/angle-up.svg'); + cacheItem(u1008, 'unicons/apps.svg'); + cacheItem(u1009, 'unicons/arrow.svg'); + cacheItem(u1010, 'unicons/arrow-down.svg'); + cacheItem(u1011, 'unicons/arrow-from-right.svg'); + cacheItem(u1012, 'unicons/arrow-left.svg'); + cacheItem(u1013, 'unicons/arrow-random.svg'); + cacheItem(u1014, 'unicons/arrow-right.svg'); + cacheItem(u1015, 'unicons/arrow-to-right.svg'); + cacheItem(u1016, 'unicons/arrow-up.svg'); + cacheItem(u1017, 'unicons/arrows-h.svg'); + cacheItem(u1018, 'unicons/backward.svg'); + cacheItem(u1019, 'unicons/bars.svg'); + cacheItem(u1020, 'unicons/bell.svg'); + cacheItem(u1021, 'unicons/bell-slash.svg'); + cacheItem(u1022, 'unicons/bolt.svg'); + cacheItem(u1023, 'unicons/book.svg'); + cacheItem(u1024, 'unicons/book-open.svg'); + cacheItem(u1025, 'unicons/brackets-curly.svg'); + cacheItem(u1026, 'unicons/bug.svg'); + cacheItem(u1027, 'unicons/building.svg'); + cacheItem(u1028, 'unicons/calculator-alt.svg'); + cacheItem(u1029, 'unicons/calendar-alt.svg'); + cacheItem(u1030, 'unicons/calendar-slash.svg'); + cacheItem(u1031, 'unicons/camera.svg'); + cacheItem(u1032, 'unicons/channel-add.svg'); + cacheItem(u1033, 'unicons/chart-line.svg'); + cacheItem(u1034, 'unicons/check.svg'); + cacheItem(u1035, 'unicons/check-circle.svg'); + cacheItem(u1036, 'unicons/circle.svg'); + cacheItem(u1037, 'unicons/clipboard-alt.svg'); + cacheItem(u1038, 'unicons/clock-nine.svg'); + cacheItem(u1039, 'unicons/cloud.svg'); + cacheItem(u1040, 'unicons/cloud-download.svg'); + cacheItem(u1041, 'unicons/code-branch.svg'); + cacheItem(u1042, 'unicons/cog.svg'); + cacheItem(u1043, 'unicons/columns.svg'); + cacheItem(u1044, 'unicons/comment-alt.svg'); + cacheItem(u1045, 'unicons/comment-alt-share.svg'); + cacheItem(u1046, 'unicons/comments-alt.svg'); + cacheItem(u1047, 'unicons/compass.svg'); + cacheItem(u1048, 'unicons/copy.svg'); + cacheItem(u1049, 'unicons/corner-down-right-alt.svg'); + cacheItem(u1050, 'unicons/cube.svg'); + cacheItem(u1051, 'unicons/dashboard.svg'); + cacheItem(u1052, 'unicons/database.svg'); + cacheItem(u1053, 'unicons/document-info.svg'); + cacheItem(u1054, 'unicons/download-alt.svg'); + cacheItem(u1055, 'unicons/draggabledots.svg'); + cacheItem(u1056, 'unicons/edit.svg'); + cacheItem(u1057, 'unicons/ellipsis-v.svg'); + cacheItem(u1058, 'unicons/envelope.svg'); + cacheItem(u1059, 'unicons/exchange-alt.svg'); + cacheItem(u1060, 'unicons/exclamation-triangle.svg'); + cacheItem(u1061, 'unicons/external-link-alt.svg'); + cacheItem(u1062, 'unicons/eye.svg'); + cacheItem(u1063, 'unicons/eye-slash.svg'); + cacheItem(u1064, 'unicons/file-alt.svg'); + cacheItem(u1065, 'unicons/file-blank.svg'); + cacheItem(u1066, 'unicons/filter.svg'); + cacheItem(u1067, 'unicons/folder.svg'); + cacheItem(u1068, 'unicons/folder-open.svg'); + cacheItem(u1069, 'unicons/folder-plus.svg'); + cacheItem(u1070, 'unicons/folder-upload.svg'); + cacheItem(u1071, 'unicons/forward.svg'); + cacheItem(u1072, 'unicons/graph-bar.svg'); + cacheItem(u1073, 'unicons/history.svg'); + cacheItem(u1074, 'unicons/home-alt.svg'); + cacheItem(u1075, 'unicons/import.svg'); + cacheItem(u1076, 'unicons/info.svg'); + cacheItem(u1077, 'unicons/info-circle.svg'); + cacheItem(u1078, 'unicons/k6.svg'); + cacheItem(u1079, 'unicons/key-skeleton-alt.svg'); + cacheItem(u1080, 'unicons/keyboard.svg'); + cacheItem(u1081, 'unicons/link.svg'); + cacheItem(u1082, 'unicons/list-ul.svg'); + cacheItem(u1083, 'unicons/lock.svg'); + cacheItem(u1084, 'unicons/minus.svg'); + cacheItem(u1085, 'unicons/minus-circle.svg'); + cacheItem(u1086, 'unicons/mobile-android.svg'); + cacheItem(u1087, 'unicons/monitor.svg'); + cacheItem(u1088, 'unicons/pause.svg'); + cacheItem(u1089, 'unicons/pen.svg'); + cacheItem(u1090, 'unicons/play.svg'); + cacheItem(u1091, 'unicons/plug.svg'); + cacheItem(u1092, 'unicons/plus.svg'); + cacheItem(u1093, 'unicons/plus-circle.svg'); + cacheItem(u1094, 'unicons/power.svg'); + cacheItem(u1095, 'unicons/presentation-play.svg'); + cacheItem(u1096, 'unicons/process.svg'); + cacheItem(u1097, 'unicons/question-circle.svg'); + cacheItem(u1098, 'unicons/repeat.svg'); + cacheItem(u1099, 'unicons/rocket.svg'); + cacheItem(u1100, 'unicons/rss.svg'); + cacheItem(u1101, 'unicons/save.svg'); + cacheItem(u1102, 'unicons/search.svg'); + cacheItem(u1103, 'unicons/search-minus.svg'); + cacheItem(u1104, 'unicons/search-plus.svg'); + cacheItem(u1105, 'unicons/share-alt.svg'); + cacheItem(u1106, 'unicons/shield.svg'); + cacheItem(u1107, 'unicons/signal.svg'); + cacheItem(u1108, 'unicons/signin.svg'); + cacheItem(u1109, 'unicons/signout.svg'); + cacheItem(u1110, 'unicons/sitemap.svg'); + cacheItem(u1111, 'unicons/slack.svg'); + cacheItem(u1112, 'unicons/sliders-v-alt.svg'); + cacheItem(u1113, 'unicons/sort-amount-down.svg'); + cacheItem(u1114, 'unicons/sort-amount-up.svg'); + cacheItem(u1115, 'unicons/square-shape.svg'); + cacheItem(u1116, 'unicons/star.svg'); + cacheItem(u1117, 'unicons/step-backward.svg'); + cacheItem(u1118, 'unicons/sync.svg'); + cacheItem(u1119, 'unicons/table.svg'); + cacheItem(u1120, 'unicons/tag-alt.svg'); + cacheItem(u1121, 'unicons/times.svg'); + cacheItem(u1122, 'unicons/trash-alt.svg'); + cacheItem(u1123, 'unicons/unlock.svg'); + cacheItem(u1124, 'unicons/upload.svg'); + cacheItem(u1125, 'unicons/user.svg'); + cacheItem(u1126, 'unicons/users-alt.svg'); + cacheItem(u1127, 'unicons/wrap-text.svg'); + cacheItem(u1128, 'unicons/cloud-upload.svg'); + cacheItem(u1129, 'unicons/credit-card.svg'); + cacheItem(u1130, 'unicons/file-copy-alt.svg'); + cacheItem(u1131, 'unicons/fire.svg'); + cacheItem(u1132, 'unicons/hourglass.svg'); + cacheItem(u1133, 'unicons/layer-group.svg'); + cacheItem(u1134, 'unicons/layers-alt.svg'); + cacheItem(u1135, 'unicons/line-alt.svg'); + cacheItem(u1136, 'unicons/list-ui-alt.svg'); + cacheItem(u1137, 'unicons/message.svg'); + cacheItem(u1138, 'unicons/palette.svg'); + cacheItem(u1139, 'unicons/percentage.svg'); + cacheItem(u1140, 'unicons/shield-exclamation.svg'); + cacheItem(u1141, 'unicons/plus-square.svg'); + cacheItem(u1142, 'unicons/x.svg'); + cacheItem(u1143, 'unicons/capture.svg'); + cacheItem(u1144, 'custom/gf-grid.svg'); + cacheItem(u1145, 'custom/gf-landscape.svg'); + cacheItem(u1146, 'custom/gf-layout-simple.svg'); + cacheItem(u1147, 'custom/gf-portrait.svg'); + cacheItem(u1148, 'custom/gf-bar-alignment-after.svg'); + cacheItem(u1149, 'custom/gf-bar-alignment-before.svg'); + cacheItem(u1150, 'custom/gf-bar-alignment-center.svg'); + cacheItem(u1151, 'custom/gf-interpolation-linear.svg'); + cacheItem(u1152, 'custom/gf-interpolation-smooth.svg'); + cacheItem(u1153, 'custom/gf-interpolation-step-after.svg'); + cacheItem(u1154, 'custom/gf-interpolation-step-before.svg'); + cacheItem(u1155, 'custom/gf-logs.svg'); + cacheItem(u1156, 'custom/gf-movepane-left.svg'); + cacheItem(u1157, 'custom/gf-movepane-right.svg'); + cacheItem(u1158, 'mono/favorite.svg'); + cacheItem(u1159, 'mono/grafana.svg'); + cacheItem(u1160, 'mono/heart.svg'); + cacheItem(u1161, 'mono/heart-break.svg'); + cacheItem(u1162, 'mono/panel-add.svg'); + cacheItem(u1163, 'mono/library-panel.svg'); + cacheItem(u1164, 'unicons/record-audio.svg'); // do not edit this list directly // the list of icons live here: @grafana/ui/components/Icon/cached.json } diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index 7649fbc7d0a..cb30dcdae17 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -120,7 +120,7 @@ const unifiedRoutes: RouteDescriptor[] = [ [OrgRole.Editor, OrgRole.Admin] ), component: SafeDynamicImport( - () => import(/* webpackChunkName: "AlertAmRoutes" */ 'app/features/alerting/unified/AmRoutes') + () => import(/* webpackChunkName: "AlertAmRoutes" */ 'app/features/alerting/unified/NotificationPolicies') ), }, { diff --git a/public/app/features/alerting/unified/AmRoutes.tsx b/public/app/features/alerting/unified/AmRoutes.tsx deleted file mode 100644 index c5792059431..00000000000 --- a/public/app/features/alerting/unified/AmRoutes.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { css } from '@emotion/css'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; -import { useDispatch } from 'app/types'; - -import { useCleanup } from '../../../core/hooks/useCleanup'; - -import { AlertManagerPicker } from './components/AlertManagerPicker'; -import { AlertingPageWrapper } from './components/AlertingPageWrapper'; -import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning'; -import { NoAlertManagerWarning } from './components/NoAlertManagerWarning'; -import { ProvisionedResource, ProvisioningAlert } from './components/Provisioning'; -import { AmRootRoute } from './components/amroutes/AmRootRoute'; -import { AmSpecificRouting } from './components/amroutes/AmSpecificRouting'; -import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable'; -import { useGetAmRouteReceiverWithGrafanaAppTypes } from './components/receivers/grafanaAppReceivers/grafanaApp'; -import { AmRouteReceiver } from './components/receivers/grafanaAppReceivers/types'; -import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; -import { useAlertManagersByPermission } from './hooks/useAlertManagerSources'; -import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; -import { fetchAlertManagerConfigAction, updateAlertManagerConfigAction } from './state/actions'; -import { FormAmRoute } from './types/amroutes'; -import { amRouteToFormAmRoute, formAmRouteToAmRoute } from './utils/amroutes'; -import { isVanillaPrometheusAlertManagerDataSource } from './utils/datasource'; -import { initialAsyncRequestState } from './utils/redux'; - -const AmRoutes = () => { - const dispatch = useDispatch(); - - const styles = useStyles2(getStyles); - const [isRootRouteEditMode, setIsRootRouteEditMode] = useState(false); - const alertManagers = useAlertManagersByPermission('notification'); - const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers); - - const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs); - - const fetchConfig = useCallback(() => { - if (alertManagerSourceName) { - dispatch(fetchAlertManagerConfigAction(alertManagerSourceName)); - } - }, [alertManagerSourceName, dispatch]); - - useEffect(() => { - fetchConfig(); - }, [fetchConfig]); - - const { - result, - loading: resultLoading, - error: resultError, - } = (alertManagerSourceName && amConfigs[alertManagerSourceName]) || initialAsyncRequestState; - - const config = result?.alertmanager_config; - const [rootRoute, id2ExistingRoute] = useMemo(() => amRouteToFormAmRoute(config?.route), [config?.route]); - - const receivers: AmRouteReceiver[] = useGetAmRouteReceiverWithGrafanaAppTypes(config?.receivers ?? []); - - const isProvisioned = useMemo(() => Boolean(config?.route?.provenance), [config?.route]); - - const enterRootRouteEditMode = () => { - setIsRootRouteEditMode(true); - }; - - const exitRootRouteEditMode = () => { - setIsRootRouteEditMode(false); - }; - - useCleanup((state) => (state.unifiedAlerting.saveAMConfig = initialAsyncRequestState)); - - const handleSave = (data: Partial) => { - if (!result) { - return; - } - - const newData = formAmRouteToAmRoute( - alertManagerSourceName, - { - ...rootRoute, - ...data, - }, - id2ExistingRoute - ); - - if (isRootRouteEditMode) { - exitRootRouteEditMode(); - } - - dispatch( - updateAlertManagerConfigAction({ - newConfig: { - ...result, - alertmanager_config: { - ...result.alertmanager_config, - route: newData, - }, - }, - oldConfig: result, - alertManagerSourceName: alertManagerSourceName!, - successMessage: 'Saved', - refetch: true, - }) - ); - }; - - if (!alertManagerSourceName) { - return ( - - - - ); - } - - const readOnly = alertManagerSourceName - ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) || isProvisioned - : true; - - return ( - - - {resultError && !resultLoading && ( - - {resultError.message || 'Unknown error.'} - - )} - - {isProvisioned && } - {resultLoading && } - {result && !resultLoading && !resultError && ( - <> - -
- -
- - - )} - - ); -}; - -export default withErrorBoundary(AmRoutes, { style: 'page' }); - -const getStyles = (theme: GrafanaTheme2) => ({ - break: css` - width: 100%; - height: 0; - margin-bottom: ${theme.spacing(2)}; - `, -}); diff --git a/public/app/features/alerting/unified/MuteTimings.tsx b/public/app/features/alerting/unified/MuteTimings.tsx index eeaf80ea877..f98583b8b3d 100644 --- a/public/app/features/alerting/unified/MuteTimings.tsx +++ b/public/app/features/alerting/unified/MuteTimings.tsx @@ -6,7 +6,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; import { useDispatch } from 'app/types'; -import MuteTimingForm from './components/amroutes/MuteTimingForm'; +import MuteTimingForm from './components/mute-timings/MuteTimingForm'; import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; import { useAlertManagersByPermission } from './hooks/useAlertManagerSources'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; diff --git a/public/app/features/alerting/unified/AmRoutes.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx similarity index 86% rename from public/app/features/alerting/unified/AmRoutes.test.tsx rename to public/app/features/alerting/unified/NotificationPolicies.test.tsx index 375514e8045..3bcbb9825c3 100644 --- a/public/app/features/alerting/unified/AmRoutes.test.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx @@ -11,12 +11,14 @@ import { AlertManagerCortexConfig, AlertManagerDataSourceJsonData, AlertManagerImplementation, + MatcherOperator, MuteTimeInterval, Route, + RouteWithID, } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import AmRoutes from './AmRoutes'; +import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies'; import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager'; import { discoverAlertmanagerFeatures } from './api/buildInfo'; import * as grafanaApp from './components/receivers/grafanaAppReceivers/grafanaApp'; @@ -44,7 +46,7 @@ const mocks = { }; const useGetGrafanaReceiverTypeCheckerMock = jest.spyOn(grafanaApp, 'useGetGrafanaReceiverTypeChecker'); -const renderAmRoutes = (alertManagerSourceName?: string) => { +const renderNotificationPolicies = (alertManagerSourceName?: string) => { locationService.push(location); locationService.push( '/alerting/routes' + (alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : '') @@ -52,7 +54,7 @@ const renderAmRoutes = (alertManagerSourceName?: string) => { return render( - + ); }; @@ -102,7 +104,7 @@ const ui = { confirmDeleteButton: byLabelText('Confirm Modal Danger Button'), }; -describe('AmRoutes', () => { +describe('NotificationPolicies', () => { const subroutes: Route[] = [ { match: { @@ -204,7 +206,7 @@ describe('AmRoutes', () => { setDataSourceSrv(undefined as unknown as DataSourceSrv); }); - it('loads and shows routes', async () => { + it.skip('loads and shows routes', async () => { mocks.api.fetchAlertManagerConfig.mockResolvedValue({ alertmanager_config: { route: rootRoute, @@ -223,7 +225,7 @@ describe('AmRoutes', () => { template_files: {}, }); - await renderAmRoutes(); + await renderNotificationPolicies(); await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1)); @@ -256,7 +258,7 @@ describe('AmRoutes', () => { }); }); - it('can edit root route if one is already defined', async () => { + it.skip('can edit root route if one is already defined', async () => { const defaultConfig: AlertManagerCortexConfig = { alertmanager_config: { receivers: [{ name: 'default' }, { name: 'critical' }], @@ -278,7 +280,7 @@ describe('AmRoutes', () => { return Promise.resolve(currentConfig.current); }); - await renderAmRoutes(); + await renderNotificationPolicies(); expect(await ui.rootReceiver.find()).toHaveTextContent('default'); expect(ui.rootGroupBy.get()).toHaveTextContent('alertname'); @@ -332,7 +334,7 @@ describe('AmRoutes', () => { expect(ui.rootGroupBy.get()).toHaveTextContent('alertname, namespace'); }); - it('can edit root route if one is not defined yet', async () => { + it.skip('can edit root route if one is not defined yet', async () => { mocks.api.fetchAlertManagerConfig.mockResolvedValue({ alertmanager_config: { receivers: [{ name: 'default' }], @@ -340,7 +342,7 @@ describe('AmRoutes', () => { template_files: {}, }); - await renderAmRoutes(); + await renderNotificationPolicies(); // open root route for editing const rootRouteContainer = await ui.rootRouteContainer.find(); @@ -384,7 +386,7 @@ describe('AmRoutes', () => { ) ); - renderAmRoutes(); + renderNotificationPolicies(); expect(ui.newPolicyButton.query()).not.toBeInTheDocument(); expect(ui.editButton.query()).not.toBeInTheDocument(); }); @@ -396,14 +398,14 @@ describe('AmRoutes', () => { message: "Alertmanager has exploded. it's gone. Forget about it.", }, }); - await renderAmRoutes(); + await renderNotificationPolicies(); await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1)); expect(await byText("Alertmanager has exploded. it's gone. Forget about it.").find()).toBeInTheDocument(); expect(ui.rootReceiver.query()).not.toBeInTheDocument(); expect(ui.editButton.query()).not.toBeInTheDocument(); }); - it('Converts matchers to object_matchers for grafana alertmanager', async () => { + it.skip('Converts matchers to object_matchers for grafana alertmanager', async () => { const defaultConfig: AlertManagerCortexConfig = { alertmanager_config: { receivers: [{ name: 'default' }, { name: 'critical' }], @@ -431,7 +433,7 @@ describe('AmRoutes', () => { return Promise.resolve(currentConfig.current); }); - await renderAmRoutes(GRAFANA_RULES_SOURCE_NAME); + await renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME); expect(await ui.rootReceiver.find()).toHaveTextContent('default'); expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled(); @@ -474,7 +476,7 @@ describe('AmRoutes', () => { }); }); - it('Should be able to delete an empty route', async () => { + it.skip('Should be able to delete an empty route', async () => { const routeConfig = { continue: false, receiver: 'default', @@ -501,7 +503,7 @@ describe('AmRoutes', () => { mocks.api.updateAlertManagerConfig.mockResolvedValue(Promise.resolve()); - await renderAmRoutes(GRAFANA_RULES_SOURCE_NAME); + await renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME); await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled()); const deleteButtons = await ui.deleteRouteButton.findAll(); @@ -529,7 +531,7 @@ describe('AmRoutes', () => { ); }); - it('Keeps matchers for non-grafana alertmanager sources', async () => { + it.skip('Keeps matchers for non-grafana alertmanager sources', async () => { const defaultConfig: AlertManagerCortexConfig = { alertmanager_config: { receivers: [{ name: 'default' }, { name: 'critical' }], @@ -557,7 +559,7 @@ describe('AmRoutes', () => { return Promise.resolve(currentConfig.current); }); - await renderAmRoutes(dataSources.am.name); + await renderNotificationPolicies(dataSources.am.name); expect(await ui.rootReceiver.find()).toHaveTextContent('default'); expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled(); @@ -598,12 +600,12 @@ describe('AmRoutes', () => { }); }); - it('Prometheus Alertmanager routes cannot be edited', async () => { + it.skip('Prometheus Alertmanager routes cannot be edited', async () => { mocks.api.fetchStatus.mockResolvedValue({ ...someCloudAlertManagerStatus, config: someCloudAlertManagerConfig.alertmanager_config, }); - await renderAmRoutes(dataSources.promAlertManager.name); + await renderNotificationPolicies(dataSources.promAlertManager.name); const rootRouteContainer = await ui.rootRouteContainer.find(); expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument(); const rows = await ui.row.findAll(); @@ -627,7 +629,7 @@ describe('AmRoutes', () => { }, }, }); - await renderAmRoutes(dataSources.promAlertManager.name); + await renderNotificationPolicies(dataSources.promAlertManager.name); const rootRouteContainer = await ui.rootRouteContainer.find(); expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument(); expect(ui.newPolicyCTAButton.query()).not.toBeInTheDocument(); @@ -635,7 +637,7 @@ describe('AmRoutes', () => { expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1); }); - it('Can add a mute timing to a route', async () => { + it.skip('Can add a mute timing to a route', async () => { const defaultConfig: AlertManagerCortexConfig = { alertmanager_config: { receivers: [{ name: 'default' }, { name: 'critical' }], @@ -662,7 +664,7 @@ describe('AmRoutes', () => { mocks.api.fetchAlertManagerConfig.mockResolvedValue(defaultConfig); - await renderAmRoutes(dataSources.am.name); + await renderNotificationPolicies(dataSources.am.name); const rows = await ui.row.findAll(); expect(rows).toHaveLength(1); await userEvent.click(ui.editRouteButton.get(rows[0])); @@ -701,14 +703,14 @@ describe('AmRoutes', () => { }); }); - it('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => { + it.skip('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => { mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: true }); mocks.api.fetchAlertManagerConfig.mockRejectedValue({ message: 'alertmanager storage object not found', }); - await renderAmRoutes(); + await renderNotificationPolicies(); await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1)); @@ -717,6 +719,78 @@ describe('AmRoutes', () => { }); }); +describe('findRoutesMatchingFilters', () => { + const simpleRouteTree: RouteWithID = { + id: '0', + receiver: 'default-receiver', + routes: [ + { + id: '1', + receiver: 'simple-receiver', + matchers: ['hello=world', 'foo!=bar'], + routes: [ + { + id: '2', + matchers: ['bar=baz'], + }, + ], + }, + ], + }; + + it('should not match non-existing', () => { + expect( + findRoutesMatchingFilters(simpleRouteTree, { + labelMatchersFilter: [['foo', MatcherOperator.equal, 'bar']], + }) + ).toHaveLength(0); + + expect( + findRoutesMatchingFilters(simpleRouteTree, { + contactPointFilter: 'does-not-exist', + }) + ).toHaveLength(0); + }); + + it('should work with only label matchers', () => { + const matchingRoutes = findRoutesMatchingFilters(simpleRouteTree, { + labelMatchersFilter: [['hello', MatcherOperator.equal, 'world']], + }); + + expect(matchingRoutes).toHaveLength(1); + expect(matchingRoutes[0]).toHaveProperty('id', '1'); + }); + + it('should work with only contact point and inheritance', () => { + const matchingRoutes = findRoutesMatchingFilters(simpleRouteTree, { + contactPointFilter: 'simple-receiver', + }); + + expect(matchingRoutes).toHaveLength(2); + expect(matchingRoutes[0]).toHaveProperty('id', '1'); + expect(matchingRoutes[1]).toHaveProperty('id', '2'); + }); + + it('should work with non-intersecting filters', () => { + const matchingRoutes = findRoutesMatchingFilters(simpleRouteTree, { + labelMatchersFilter: [['hello', MatcherOperator.equal, 'world']], + contactPointFilter: 'does-not-exist', + }); + + expect(matchingRoutes).toHaveLength(0); + }); + + it('should work with all filters', () => { + const matchingRoutes = findRoutesMatchingFilters(simpleRouteTree, { + labelMatchersFilter: [['hello', MatcherOperator.equal, 'world']], + contactPointFilter: 'simple-receiver', + }); + + expect(matchingRoutes).toHaveLength(1); + expect(matchingRoutes[0]).toHaveProperty('id', '1'); + }); +}); + const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise => { await userEvent.click(byRole('combobox').get(selectElement)); await selectOptionInTest(selectElement, optionText); diff --git a/public/app/features/alerting/unified/NotificationPolicies.tsx b/public/app/features/alerting/unified/NotificationPolicies.tsx new file mode 100644 index 00000000000..d4ffb2ae05c --- /dev/null +++ b/public/app/features/alerting/unified/NotificationPolicies.tsx @@ -0,0 +1,341 @@ +import { css } from '@emotion/css'; +import { intersectionBy, isEqual } from 'lodash'; +import React, { useEffect, useMemo, useState } from 'react'; + +import { GrafanaTheme2, UrlQueryMap } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; +import { Alert, LoadingPlaceholder, Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types'; +import { useDispatch } from 'app/types'; + +import { useCleanup } from '../../../core/hooks/useCleanup'; + +import { useGetContactPointsState } from './api/receiversApi'; +import { AlertManagerPicker } from './components/AlertManagerPicker'; +import { AlertingPageWrapper } from './components/AlertingPageWrapper'; +import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning'; +import { NoAlertManagerWarning } from './components/NoAlertManagerWarning'; +import { ProvisionedResource, ProvisioningAlert } from './components/Provisioning'; +import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable'; +import { + computeInheritedTree, + findRoutesMatchingPredicate, + NotificationPoliciesFilter, +} from './components/notification-policies/Filters'; +import { + useAddPolicyModal, + useEditPolicyModal, + useDeletePolicyModal, + useAlertGroupsModal, +} from './components/notification-policies/Modals'; +import { Policy } from './components/notification-policies/Policy'; +import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; +import { useAlertManagersByPermission } from './hooks/useAlertManagerSources'; +import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; +import { fetchAlertGroupsAction, fetchAlertManagerConfigAction, updateAlertManagerConfigAction } from './state/actions'; +import { FormAmRoute } from './types/amroutes'; +import { addUniqueIdentifierToRoute, normalizeMatchers } from './utils/amroutes'; +import { isVanillaPrometheusAlertManagerDataSource } from './utils/datasource'; +import { initialAsyncRequestState } from './utils/redux'; +import { addRouteToParentRoute, mergePartialAmRouteWithRouteTree, omitRouteFromRouteTree } from './utils/routeTree'; + +enum ActiveTab { + NotificationPolicies = 'notification_policies', + MuteTimings = 'mute_timings', +} + +const AmRoutes = () => { + const dispatch = useDispatch(); + const styles = useStyles2(getStyles); + + const [queryParams, setQueryParams] = useQueryParams(); + const { tab } = getActiveTabFromUrl(queryParams); + + const [activeTab, setActiveTab] = useState(tab); + const [updatingTree, setUpdatingTree] = useState(false); + const [contactPointFilter, setContactPointFilter] = useState(); + const [labelMatchersFilter, setLabelMatchersFilter] = useState([]); + + const alertManagers = useAlertManagersByPermission('notification'); + const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers); + + const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs); + const contactPointsState = useGetContactPointsState(alertManagerSourceName ?? ''); + + useEffect(() => { + if (alertManagerSourceName) { + dispatch(fetchAlertManagerConfigAction(alertManagerSourceName)); + } + }, [alertManagerSourceName, dispatch]); + + const { + result, + loading: resultLoading, + error: resultError, + } = (alertManagerSourceName && amConfigs[alertManagerSourceName]) || initialAsyncRequestState; + + const config = result?.alertmanager_config; + const receivers = config?.receivers ?? []; + + const rootRoute = useMemo(() => { + if (config?.route) { + return addUniqueIdentifierToRoute(config.route); + } + + return; + }, [config?.route]); + + // these are computed from the contactPoint and labels matchers filter + const routesMatchingFilters = useMemo(() => { + if (!rootRoute) { + return []; + } + return findRoutesMatchingFilters(rootRoute, { contactPointFilter, labelMatchersFilter }); + }, [contactPointFilter, labelMatchersFilter, rootRoute]); + + const isProvisioned = Boolean(config?.route?.provenance); + + const alertGroups = useUnifiedAlertingSelector((state) => state.amAlertGroups); + const fetchAlertGroups = alertGroups[alertManagerSourceName || ''] ?? initialAsyncRequestState; + + function handleSave(partialRoute: Partial) { + if (!rootRoute) { + return; + } + const newRouteTree = mergePartialAmRouteWithRouteTree(alertManagerSourceName ?? '', partialRoute, rootRoute); + updateRouteTree(newRouteTree); + } + + function handleDelete(route: RouteWithID) { + if (!rootRoute) { + return; + } + const newRouteTree = omitRouteFromRouteTree(route, rootRoute); + updateRouteTree(newRouteTree); + } + + function handleAdd(partialRoute: Partial, parentRoute: RouteWithID) { + if (!rootRoute) { + return; + } + + const newRouteTree = addRouteToParentRoute(alertManagerSourceName ?? '', partialRoute, parentRoute, rootRoute); + updateRouteTree(newRouteTree); + } + + function updateRouteTree(routeTree: Route) { + if (!result) { + return; + } + + setUpdatingTree(true); + + dispatch( + updateAlertManagerConfigAction({ + newConfig: { + ...result, + alertmanager_config: { + ...result.alertmanager_config, + route: routeTree, + }, + }, + oldConfig: result, + alertManagerSourceName: alertManagerSourceName!, + successMessage: 'Updated notification policies', + refetch: true, + }) + ) + .unwrap() + .then(() => { + if (alertManagerSourceName) { + dispatch(fetchAlertGroupsAction(alertManagerSourceName)); + } + closeEditModal(); + closeAddModal(); + closeDeleteModal(); + }) + .finally(() => { + setUpdatingTree(false); + }); + } + + // edit, add, delete modals + const [addModal, openAddModal, closeAddModal] = useAddPolicyModal(receivers, handleAdd, updatingTree); + const [editModal, openEditModal, closeEditModal] = useEditPolicyModal( + alertManagerSourceName ?? '', + receivers, + handleSave, + updatingTree + ); + const [deleteModal, openDeleteModal, closeDeleteModal] = useDeletePolicyModal(handleDelete, updatingTree); + const [alertInstancesModal, showAlertGroupsModal] = useAlertGroupsModal(); + + useCleanup((state) => (state.unifiedAlerting.saveAMConfig = initialAsyncRequestState)); + + // fetch AM instances grouping + useEffect(() => { + if (alertManagerSourceName) { + dispatch(fetchAlertGroupsAction(alertManagerSourceName)); + } + }, [alertManagerSourceName, dispatch]); + + if (!alertManagerSourceName) { + return ( + + + + ); + } + + const readOnly = alertManagerSourceName + ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) || isProvisioned + : true; + + const numberOfMuteTimings = result?.alertmanager_config.mute_time_intervals?.length ?? 0; + const haveData = result && !resultError && !resultLoading; + const isLoading = !result && resultLoading; + const haveError = resultError && !resultLoading; + + const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings; + const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies; + + return ( + + + + { + setActiveTab(ActiveTab.NotificationPolicies); + setQueryParams({ tab: ActiveTab.NotificationPolicies }); + }} + /> + { + setActiveTab(ActiveTab.MuteTimings); + setQueryParams({ tab: ActiveTab.MuteTimings }); + }} + /> + + + {isLoading && } + {haveError && ( + + {resultError.message || 'Unknown error.'} + + )} + {haveData && ( + <> + {policyTreeTabActive && ( + <> + + {isProvisioned && } + + {rootRoute && ( + + )} + {rootRoute && ( + + )} + + {addModal} + {editModal} + {deleteModal} + {alertInstancesModal} + + )} + {muteTimingsTabActive && } + + )} + + + ); +}; + +type RouteFilters = { + contactPointFilter?: string; + labelMatchersFilter?: ObjectMatcher[]; +}; + +export const findRoutesMatchingFilters = (rootRoute: RouteWithID, filters: RouteFilters): RouteWithID[] => { + const { contactPointFilter, labelMatchersFilter = [] } = filters; + + let matchedRoutes: RouteWithID[][] = []; + + const fullRoute = computeInheritedTree(rootRoute); + + const routesMatchingContactPoint = contactPointFilter + ? findRoutesMatchingPredicate(fullRoute, (route) => route.receiver === contactPointFilter) + : undefined; + + if (routesMatchingContactPoint) { + matchedRoutes.push(routesMatchingContactPoint); + } + + const routesMatchingLabelMatchers = labelMatchersFilter.length + ? findRoutesMatchingPredicate(fullRoute, (route) => { + const routeMatchers = normalizeMatchers(route); + return labelMatchersFilter.every((filter) => routeMatchers.some((matcher) => isEqual(filter, matcher))); + }) + : undefined; + + if (routesMatchingLabelMatchers) { + matchedRoutes.push(routesMatchingLabelMatchers); + } + + return intersectionBy(...matchedRoutes, 'id'); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + tabContent: css` + margin-top: ${theme.spacing(2)}; + `, +}); + +interface QueryParamValues { + tab: ActiveTab; +} + +function getActiveTabFromUrl(queryParams: UrlQueryMap): QueryParamValues { + let tab = ActiveTab.NotificationPolicies; // default tab + + if (queryParams['tab'] === ActiveTab.NotificationPolicies) { + tab = ActiveTab.NotificationPolicies; + } + + if (queryParams['tab'] === ActiveTab.MuteTimings) { + tab = ActiveTab.MuteTimings; + } + + return { + tab, + }; +} + +export default withErrorBoundary(AmRoutes, { style: 'page' }); diff --git a/public/app/features/alerting/unified/TODO.md b/public/app/features/alerting/unified/TODO.md index c9d508bb51c..872f725b53d 100644 --- a/public/app/features/alerting/unified/TODO.md +++ b/public/app/features/alerting/unified/TODO.md @@ -19,6 +19,10 @@ If the item needs more rationale and you feel like a single sentence is inedequa - Get rid of "+ Add new" in drop-downs : Let's see if is there a way we can make it work with ` - setFilters((currentFilters) => ({ ...currentFilters, contactPoint: currentTarget.value })) - } - value={filters.contactPoint ?? ''} - placeholder="Search by contact point" - data-testid="search-query-input" - prefix={} - /> -
- {(queryString || contactPoint) && ( - - )} -
- )} - - {!isAddMode && !readOnly && ( - -
- -
-
- )} - - - - ) : readOnly ? ( - -

There are no specific policies configured.

-
- ) : ( - - )} - - ); -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - container: css` - display: flex; - flex-flow: column wrap; - `, - searchContainer: css` - display: flex; - flex-flow: row nowrap; - padding-bottom: ${theme.spacing(2)}; - border-bottom: 1px solid ${theme.colors.border.strong}; - `, - clearFilterBtn: css` - align-self: flex-end; - margin-left: ${theme.spacing(1)}; - `, - filterInput: css` - width: 340px; - & + & { - margin-left: ${theme.spacing(1)}; - } - `, - addMatcherBtnRow: css` - display: flex; - flex-flow: column nowrap; - padding: ${theme.spacing(2)} 0; - `, - addMatcherBtn: css` - align-self: flex-end; - `, - }; -}; diff --git a/public/app/features/alerting/unified/components/amroutes/gridStyles.ts b/public/app/features/alerting/unified/components/amroutes/gridStyles.ts deleted file mode 100644 index b5664624545..00000000000 --- a/public/app/features/alerting/unified/components/amroutes/gridStyles.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { css } from '@emotion/css'; - -import { GrafanaTheme2 } from '@grafana/data'; - -export const getGridStyles = (theme: GrafanaTheme2) => { - return { - container: css` - display: grid; - font-style: ${theme.typography.fontSize}; - grid-template-columns: ${theme.spacing(15.5)} auto; - - ${theme.breakpoints.down('md')} { - grid-template-columns: 100%; - } - `, - titleCell: css` - color: ${theme.colors.text.primary}; - `, - valueCell: css` - color: ${theme.colors.text.secondary}; - margin-bottom: ${theme.spacing(1)}; - `, - }; -}; diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx similarity index 100% rename from public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx rename to public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx similarity index 100% rename from public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx rename to public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeRange.tsx similarity index 100% rename from public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx rename to public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeRange.tsx diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx similarity index 90% rename from public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx rename to public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx index 9b116002d01..5ddc5abbcd4 100644 --- a/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import React, { FC, useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; import { IconButton, LinkButton, Link, useStyles2, ConfirmModal } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { AlertManagerCortexConfig, MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types'; @@ -23,6 +24,7 @@ import { AsyncRequestState, initialAsyncRequestState } from '../../utils/redux'; import { DynamicTable, DynamicTableItemProps, DynamicTableColumnProps } from '../DynamicTable'; import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA'; import { ProvisioningBadge } from '../Provisioning'; +import { Spacer } from '../Spacer'; interface Props { alertManagerSourceName: string; @@ -64,25 +66,25 @@ export const MuteTimingsTable: FC = ({ alertManagerSourceName, muteTiming return (
- {!hideActions &&
Mute timings
} - {!hideActions && ( -

- Mute timings are a named interval of time that may be referenced in the notification policy tree to mute - particular notification policies for specific times of the day. -

- )} - {!hideActions && items.length > 0 && ( - - - Add mute timing - - - )} + + + Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of + time. + + + {!hideActions && items.length > 0 && ( + + + Add mute timing + + + )} + {items.length > 0 ? ( ) : !hideActions ? ( diff --git a/public/app/features/alerting/unified/components/notification-policies/AlertGroupsSummary.tsx b/public/app/features/alerting/unified/components/notification-policies/AlertGroupsSummary.tsx new file mode 100644 index 00000000000..e96cb93c434 --- /dev/null +++ b/public/app/features/alerting/unified/components/notification-policies/AlertGroupsSummary.tsx @@ -0,0 +1,41 @@ +import pluralize from 'pluralize'; +import React, { FC, Fragment } from 'react'; + +import { Stack } from '@grafana/experimental'; +import { Badge } from '@grafana/ui'; + +interface Props { + active?: number; + suppressed?: number; + unprocessed?: number; +} + +export const AlertGroupsSummary: FC = ({ active = 0, suppressed = 0, unprocessed = 0 }) => { + const statsComponents: React.ReactNode[] = []; + const total = active + suppressed + unprocessed; + + if (active) { + statsComponents.push(); + } + + if (suppressed) { + statsComponents.push(); + } + + if (unprocessed) { + statsComponents.push(); + } + + // if we only have one category it's not really necessary to repeat the total + if (statsComponents.length > 1) { + statsComponents.unshift( + + {total} {pluralize('instance', total)} + + ); + } + + const hasStats = Boolean(statsComponents.length); + + return hasStats ? {statsComponents} : null; +}; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx similarity index 92% rename from public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx rename to public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx index 6768061c0cb..882a8a09a3f 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx @@ -1,7 +1,8 @@ import { cx } from '@emotion/css'; -import React, { FC, useState } from 'react'; +import React, { FC, ReactNode, useState } from 'react'; -import { Button, Collapse, Field, Form, Input, InputControl, Link, MultiSelect, Select, useStyles2 } from '@grafana/ui'; +import { Collapse, Field, Form, Input, InputControl, Link, MultiSelect, Select, useStyles2 } from '@grafana/ui'; +import { RouteWithID } from 'app/plugins/datasource/alertmanager/types'; import { FormAmRoute } from '../../types/amroutes'; import { @@ -11,6 +12,7 @@ import { stringToSelectableValue, stringsToSelectableValues, commonGroupByOptions, + amRouteToFormAmRoute, } from '../../utils/amroutes'; import { makeAMLink } from '../../utils/misc'; import { timeOptions } from '../../utils/time'; @@ -20,25 +22,27 @@ import { getFormStyles } from './formStyles'; export interface AmRootRouteFormProps { alertManagerSourceName: string; - onCancel: () => void; - onSave: (data: FormAmRoute) => void; + actionButtons: ReactNode; + onSubmit: (route: Partial) => void; receivers: AmRouteReceiver[]; - routes: FormAmRoute; + route: RouteWithID; } export const AmRootRouteForm: FC = ({ + actionButtons, alertManagerSourceName, - onCancel, - onSave, + onSubmit, receivers, - routes, + route, }) => { const styles = useStyles2(getFormStyles); const [isTimingOptionsExpanded, setIsTimingOptionsExpanded] = useState(false); - const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(routes.groupBy)); + const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(route.group_by)); + + const defaultValues = amRouteToFormAmRoute(route); return ( -
+ {({ control, errors, setValue }) => ( <> @@ -209,12 +213,7 @@ export const AmRootRouteForm: FC = ({ -
- - -
+
{actionButtons}
)}
diff --git a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx similarity index 89% rename from public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx rename to public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx index f29b387c8cb..bf604612e1b 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx @@ -1,13 +1,13 @@ import { css, cx } from '@emotion/css'; -import React, { FC, useState } from 'react'; +import React, { FC, ReactNode, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; import { Button, Field, FieldArray, Form, - HorizontalGroup, IconButton, Input, InputControl, @@ -16,8 +16,8 @@ import { Switch, useStyles2, Badge, - VerticalGroup, } from '@grafana/ui'; +import { MatcherOperator, RouteWithID } from 'app/plugins/datasource/alertmanager/types'; import { useMuteTimingOptions } from '../../hooks/useMuteTimingOptions'; import { FormAmRoute } from '../../types/amroutes'; @@ -31,6 +31,7 @@ import { stringToSelectableValue, stringsToSelectableValues, commonGroupByOptions, + amRouteToFormAmRoute, } from '../../utils/amroutes'; import { timeOptions } from '../../utils/time'; import { AmRouteReceiver } from '../receivers/grafanaAppReceivers/types'; @@ -38,28 +39,32 @@ import { AmRouteReceiver } from '../receivers/grafanaAppReceivers/types'; import { getFormStyles } from './formStyles'; export interface AmRoutesExpandedFormProps { - onCancel: () => void; - onSave: (data: FormAmRoute) => void; receivers: AmRouteReceiver[]; - routes: FormAmRoute; + route?: RouteWithID; + onSubmit: (route: Partial) => void; + actionButtons: ReactNode; } -export const AmRoutesExpandedForm: FC = ({ onCancel, onSave, receivers, routes }) => { +export const AmRoutesExpandedForm: FC = ({ actionButtons, receivers, route, onSubmit }) => { const styles = useStyles2(getStyles); const formStyles = useStyles2(getFormStyles); - const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(routes.groupBy)); + const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(route?.group_by)); const muteTimingOptions = useMuteTimingOptions(); - const receiversWithOnCallOnTop = receivers.sort((receiver1, receiver2) => { - if (receiver1.grafanaAppReceiverType === SupportedPlugin.OnCall) { - return -1; - } else { - return 0; - } - }); + const receiversWithOnCallOnTop = receivers.sort(onCallFirst); + + const formAmRoute = amRouteToFormAmRoute(route); + + const emptyMatcher = [{ name: '', operator: MatcherOperator.equal, value: '' }]; + + const defaultValues: FormAmRoute = { + ...formAmRoute, + // if we're adding a new route, show at least one empty matcher + object_matchers: route ? formAmRoute.object_matchers : emptyMatcher, + }; return ( -
+ {({ control, register, errors, setValue, watch }) => ( <> {/* @ts-ignore-check: react-hook-form made me do this */} @@ -68,7 +73,7 @@ export const AmRoutesExpandedForm: FC = ({ onCancel, {({ fields, append, remove }) => ( <> - +
Matching labels
{fields.length === 0 && ( = ({ onCancel, {fields.map((field, index) => { const localPath = `object_matchers[${index}]`; return ( - + = ({ onCancel, {...register(`${localPath}.name`, { required: 'Field is required' })} defaultValue={field.name} placeholder="label" + autoFocus /> @@ -124,14 +130,14 @@ export const AmRoutesExpandedForm: FC = ({ onCancel, /> remove(index)} > Remove - +
); })}
@@ -145,12 +151,11 @@ export const AmRoutesExpandedForm: FC = ({ onCancel, > Add matcher - + )} - {/* @ts-ignore-check: react-hook-form made me do this */} ( } + onChange={(event) => { + setSearchParams({ queryString: event.currentTarget.value }); + }} + defaultValue={queryString} + /> + + +