+
+ The new rule will NOT be marked as a provisioned rule.
+
+
+ You will need to set a new alert group for the cloned rule because the original one has been provisioned
+ and cannot be used for rules created in the UI.
+
+
+ }
+ confirmText="Clone"
+ onConfirm={() => provRuleCloneUrl && locationService.push(provRuleCloneUrl)}
+ onDismiss={() => setProvRuleCloneUrl(undefined)}
+ />
>
);
}
@@ -199,20 +213,10 @@ function inViewMode(pathname: string): boolean {
}
export const getStyles = (theme: GrafanaTheme2) => ({
- wrapper: css`
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- flex-wrap: wrap;
- `,
button: css`
- height: 24px;
- font-size: ${theme.typography.size.sm};
- svg {
- margin-right: 0;
- }
+ padding: 0 ${theme.spacing(2)};
`,
- buttonText: css`
- margin-left: 8px;
+ bold: css`
+ font-weight: ${theme.typography.fontWeightBold};
`,
});
diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx
index 31b0241fb46..e53ad1f831c 100644
--- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx
@@ -83,13 +83,13 @@ describe('RulesTable RBAC', () => {
it('Should render Edit button for users with the update permission', () => {
mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true });
renderRulesTable(grafanaRule);
- expect(ui.actionButtons.edit.query()).toBeInTheDocument();
+ expect(ui.actionButtons.edit.get()).toBeInTheDocument();
});
it('Should render Delete button for users with the delete permission', () => {
mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true });
renderRulesTable(grafanaRule);
- expect(ui.actionButtons.delete.query()).toBeInTheDocument();
+ expect(ui.actionButtons.delete.get()).toBeInTheDocument();
});
});
@@ -110,13 +110,13 @@ describe('RulesTable RBAC', () => {
it('Should render Edit button for users with the update permission', () => {
mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true });
renderRulesTable(cloudRule);
- expect(ui.actionButtons.edit.query()).toBeInTheDocument();
+ expect(ui.actionButtons.edit.get()).toBeInTheDocument();
});
it('Should render Delete button for users with the delete permission', () => {
mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true });
renderRulesTable(cloudRule);
- expect(ui.actionButtons.delete.query()).toBeInTheDocument();
+ expect(ui.actionButtons.delete.get()).toBeInTheDocument();
});
});
});
diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
index affa11b32f2..5e1c0fddb0e 100644
--- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
@@ -196,7 +196,7 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
renderCell: ({ data: rule }) => {
return ;
},
- size: '290px',
+ size: '200px',
});
return columns;
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 492a6841313..89b8742000c 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -121,6 +121,7 @@ export const mockRulerAlertingRule = (partial: Partial = {
annotations: {
summary: 'test alert',
},
+ ...partial,
});
export const mockRulerRuleGroup = (partial: Partial = {}): RulerRuleGroupDTO => ({
diff --git a/public/app/features/alerting/unified/mocks/grafanaApi.ts b/public/app/features/alerting/unified/mocks/grafanaApi.ts
new file mode 100644
index 00000000000..04c45c7bb65
--- /dev/null
+++ b/public/app/features/alerting/unified/mocks/grafanaApi.ts
@@ -0,0 +1,8 @@
+import { rest } from 'msw';
+import { SetupServerApi } from 'msw/node';
+
+import { DashboardSearchItem } from '../../../search/types';
+
+export function mockSearchApiResponse(server: SetupServerApi, searchResult: DashboardSearchItem[]) {
+ server.use(rest.get('/api/search', (req, res, ctx) => res(ctx.json(searchResult))));
+}
diff --git a/public/app/features/alerting/unified/mocks/rulerApi.ts b/public/app/features/alerting/unified/mocks/rulerApi.ts
new file mode 100644
index 00000000000..18cd13b6e4c
--- /dev/null
+++ b/public/app/features/alerting/unified/mocks/rulerApi.ts
@@ -0,0 +1,30 @@
+import { rest } from 'msw';
+import { SetupServerApi } from 'msw/node';
+
+import { RulerRuleGroupDTO, RulerRulesConfigDTO } from '../../../../types/unified-alerting-dto';
+
+export function mockRulerRulesApiResponse(
+ server: SetupServerApi,
+ rulesSourceName: string,
+ response: RulerRulesConfigDTO
+) {
+ server.use(
+ rest.get(`/api/ruler/${rulesSourceName}/api/v1/rules`, (req, res, ctx) =>
+ res(ctx.json(response))
+ )
+ );
+}
+
+export function mockRulerRulesGroupApiResponse(
+ server: SetupServerApi,
+ rulesSourceName: string,
+ namespace: string,
+ group: string,
+ response: RulerRuleGroupDTO
+) {
+ server.use(
+ rest.get(`/api/ruler/${rulesSourceName}/api/v1/rules/${namespace}/${group}`, (req, res, ctx) =>
+ res(ctx.json(response))
+ )
+ );
+}
diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts
index ef6a694a1bf..66ea5f64292 100644
--- a/public/app/features/alerting/unified/utils/misc.ts
+++ b/public/app/features/alerting/unified/utils/misc.ts
@@ -1,7 +1,6 @@
import { sortBy } from 'lodash';
-import { urlUtil, UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
-import { config } from '@grafana/runtime';
+import { UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules';
import { SortOrder } from 'app/plugins/panel/alertlist/types';
import { Alert, CombinedRule, FilterState, RulesSource, SilenceFilterState } from 'app/types/unified-alerting';
@@ -15,6 +14,7 @@ import { ALERTMANAGER_NAME_QUERY_KEY } from './constants';
import { getRulesSourceName } from './datasource';
import { getMatcherQueryParams } from './matchers';
import * as ruleId from './rule-id';
+import { createUrl } from './url';
export function createViewLink(ruleSource: RulesSource, rule: CombinedRule, returnTo: string): string {
const sourceName = getRulesSourceName(ruleSource);
@@ -22,11 +22,11 @@ export function createViewLink(ruleSource: RulesSource, rule: CombinedRule, retu
const paramId = encodeURIComponent(ruleId.stringifyIdentifier(identifier));
const paramSource = encodeURIComponent(sourceName);
- return urlUtil.renderUrl(`${config.appSubUrl}/alerting/${paramSource}/${paramId}/view`, { returnTo });
+ return createUrl(`/alerting/${paramSource}/${paramId}/view`, { returnTo });
}
export function createExploreLink(dataSourceName: string, query: string) {
- return urlUtil.renderUrl(`${config.appSubUrl}/explore`, {
+ return createUrl(`/explore`, {
left: JSON.stringify([
'now-1h',
'now',
@@ -95,15 +95,15 @@ export function makeLabelBasedSilenceLink(alertManagerSourceName: string, labels
const matcherParams = getMatcherQueryParams(labels);
matcherParams.forEach((value, key) => silenceUrlParams.append(key, value));
- return `${config.appSubUrl}/alerting/silence/new?${silenceUrlParams.toString()}`;
+ return createUrl('/alerting/silence/new', silenceUrlParams);
}
export function makeDataSourceLink(dataSource: DataSourceInstanceSettings) {
- return `${config.appSubUrl}/datasources/edit/${dataSource.uid}`;
+ return createUrl(`/datasources/edit/${dataSource.uid}`);
}
export function makeFolderLink(folderUID: string): string {
- return `${config.appSubUrl}/dashboards/f/${folderUID}`;
+ return createUrl(`/dashboards/f/${folderUID}`);
}
// keep retrying fn if it's error passes shouldRetry(error) and timeout has not elapsed yet
diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts
index 3a2aa8b8884..6386541891a 100644
--- a/public/app/features/alerting/unified/utils/rules.ts
+++ b/public/app/features/alerting/unified/utils/rules.ts
@@ -145,3 +145,18 @@ export function getFirstActiveAt(promRule: AlertingRule) {
export function isFederatedRuleGroup(group: CombinedRuleGroup) {
return Array.isArray(group.source_tenants);
}
+
+export function getRuleName(rule: RulerRuleDTO) {
+ if (isGrafanaRulerRule(rule)) {
+ return rule.grafana_alert.title;
+ }
+ if (isAlertingRulerRule(rule)) {
+ return rule.alert;
+ }
+
+ if (isRecordingRulerRule(rule)) {
+ return rule.record;
+ }
+
+ return '';
+}
diff --git a/public/app/features/alerting/unified/utils/url.ts b/public/app/features/alerting/unified/utils/url.ts
new file mode 100644
index 00000000000..8499598eaa0
--- /dev/null
+++ b/public/app/features/alerting/unified/utils/url.ts
@@ -0,0 +1,6 @@
+import { config } from '@grafana/runtime';
+
+export function createUrl(path: string, queryParams?: string[][] | Record | string | URLSearchParams) {
+ const searchParams = new URLSearchParams(queryParams);
+ return `${config.appSubUrl}${path}?${searchParams.toString()}`;
+}