mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
Alerting: Add Alerting menu in getPanelMenu (#76618)
* Add Alerting menu in getPanelMenu
* Add translations
* Allow alert tab, heart icon in all panel types, and not show warning in DashobardPicker panels
* Fix tests
* Move alerting submenu under 'More...' item
* Move create alert menu item to More... without submenu
* Update translations
* Revert "Allow alert tab, heart icon in all panel types, and not show warning in DashobardPicker panels"
This reverts commit 225da3f60e
.
* Revert allowing alert tab and health icon for all panel types
* use onCreateAlert method name in onClick instead of new function
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
* Move getAlertingMenuAvailability method to a /features/alerting folder and rename it to getCreateAlertInMenuAvailability
* Use onCreate direclty instead of a new method
* Make getCreateAlertInMenuAvailability to return a boolean
---------
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
a851750b1c
commit
1de65bb384
@ -296,39 +296,39 @@ describe('AnnotationsField', function () {
|
||||
expect(annotationKeyElements[1]).toHaveTextContent('Panel ID');
|
||||
expect(annotationValueElements[1]).toHaveTextContent('3');
|
||||
});
|
||||
|
||||
it('should render warning icon for panels of type other than graph and timeseries', async function () {
|
||||
mockSearchApiResponse(server, [
|
||||
mockDashboardSearchItem({ title: 'My dashboard', uid: 'dash-test-uid', type: DashboardSearchItemType.DashDB }),
|
||||
]);
|
||||
|
||||
mockGetDashboardResponse(
|
||||
mockDashboardDto({
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel', type: 'bar' },
|
||||
{ id: 2, title: 'Second panel', type: 'graph' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<FormWrapper formValues={{ annotations: [] }} />);
|
||||
|
||||
const { dialog } = ui.dashboardPicker;
|
||||
|
||||
await user.click(ui.setDashboardButton.get());
|
||||
await user.click(await findByTitle(dialog.get(), 'My dashboard'));
|
||||
|
||||
const warnedPanel = await findByRole(dialog.get(), 'button', { name: /First panel/ });
|
||||
|
||||
expect(getByTestId(warnedPanel, 'warning-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should render warning icon for panels of type other than graph and timeseries', async function () {
|
||||
mockSearchApiResponse(server, [
|
||||
mockDashboardSearchItem({ title: 'My dashboard', uid: 'dash-test-uid', type: DashboardSearchItemType.DashDB }),
|
||||
]);
|
||||
|
||||
mockGetDashboardResponse(
|
||||
mockDashboardDto({
|
||||
title: 'My dashboard',
|
||||
uid: 'dash-test-uid',
|
||||
panels: [
|
||||
{ id: 1, title: 'First panel', type: 'bar' },
|
||||
{ id: 2, title: 'Second panel', type: 'graph' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<FormWrapper formValues={{ annotations: [] }} />);
|
||||
|
||||
const { dialog } = ui.dashboardPicker;
|
||||
|
||||
await user.click(ui.setDashboardButton.get());
|
||||
await user.click(await findByTitle(dialog.get(), 'My dashboard'));
|
||||
|
||||
const warnedPanel = await findByRole(dialog.get(), 'button', { name: /First panel/ });
|
||||
|
||||
expect(getByTestId(warnedPanel, 'warning-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
function mockGetDashboardResponse(dashboard: DashboardDTO) {
|
||||
server.use(
|
||||
rest.get(`/api/dashboards/uid/${dashboard.dashboard.uid}`, (req, res, ctx) =>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { isGrafanaRulesSource } from './datasource';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource';
|
||||
|
||||
type RulesSourceType = 'grafana' | 'external';
|
||||
|
||||
@ -128,3 +129,12 @@ export function getRulesAccess() {
|
||||
contextSrv.hasPermission(provisioningPermissions.readSecrets),
|
||||
};
|
||||
}
|
||||
|
||||
export function getCreateAlertInMenuAvailability() {
|
||||
const { unifiedAlertingEnabled } = getConfig();
|
||||
const hasRuleReadPermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).read);
|
||||
const hasRuleUpdatePermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).update);
|
||||
const isAlertingAvailableForRead = unifiedAlertingEnabled && hasRuleReadPermissions;
|
||||
|
||||
return isAlertingAvailableForRead && hasRuleUpdatePermissions;
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { AngularComponent, getPluginLinkExtensions } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
||||
import * as actions from 'app/features/explore/state/main';
|
||||
import { setStore } from 'app/store/store';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { PanelModel } from '../state';
|
||||
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
|
||||
@ -23,6 +25,7 @@ import { getPanelMenu } from './getPanelMenu';
|
||||
jest.mock('app/core/services/context_srv', () => ({
|
||||
contextSrv: {
|
||||
hasAccessToExplore: () => true,
|
||||
hasPermission: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@ -38,6 +41,8 @@ describe('getPanelMenu()', () => {
|
||||
beforeEach(() => {
|
||||
getPluginLinkExtensionsMock.mockRestore();
|
||||
getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] });
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]);
|
||||
config.unifiedAlertingEnabled = false;
|
||||
});
|
||||
|
||||
it('should return the correct panel menu items', () => {
|
||||
@ -619,4 +624,57 @@ describe('getPanelMenu()', () => {
|
||||
expect(windowOpen).toHaveBeenLastCalledWith(`${testSubUrl}${testUrl}`);
|
||||
});
|
||||
});
|
||||
describe('Alerting menu', () => {
|
||||
it('should render Create alert menu item if user has permissions to read and update alerts ', () => {
|
||||
const panel = new PanelModel({});
|
||||
|
||||
const dashboard = createDashboardModelFixture({});
|
||||
config.unifiedAlertingEnabled = true;
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]);
|
||||
const menuItems = getPanelMenu(dashboard, panel);
|
||||
const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu;
|
||||
|
||||
expect(moreSubMenu).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
text: 'Create alert',
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render Create alert menu item, if user does not have permissions to update alerts ', () => {
|
||||
const panel = new PanelModel({});
|
||||
const dashboard = createDashboardModelFixture({});
|
||||
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
config.unifiedAlertingEnabled = true;
|
||||
|
||||
const menuItems = getPanelMenu(dashboard, panel);
|
||||
|
||||
const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu;
|
||||
|
||||
expect(moreSubMenu).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
text: 'Create alert',
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
it('should not render Create alert menu item, if user does not have permissions to read update alerts ', () => {
|
||||
const panel = new PanelModel({});
|
||||
|
||||
const dashboard = createDashboardModelFixture({});
|
||||
grantUserPermissions([]);
|
||||
config.unifiedAlertingEnabled = true;
|
||||
|
||||
const menuItems = getPanelMenu(dashboard, panel);
|
||||
|
||||
const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu;
|
||||
const createAlertOption = moreSubMenu?.find((i) => i.text === 'Create alert')?.subMenu;
|
||||
|
||||
expect(createAlertOption).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,14 +3,16 @@ import {
|
||||
PanelMenuItem,
|
||||
PluginExtensionLink,
|
||||
PluginExtensionPoints,
|
||||
urlUtil,
|
||||
type PluginExtensionPanelContext,
|
||||
} from '@grafana/data';
|
||||
import { AngularComponent, locationService, reportInteraction, getPluginLinkExtensions } from '@grafana/runtime';
|
||||
import { AngularComponent, getPluginLinkExtensions, locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
|
||||
import config from 'app/core/config';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getExploreUrl } from 'app/core/utils/explore';
|
||||
import { panelToRuleFormValues } from 'app/features/alerting/unified/utils/rule-form';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import {
|
||||
@ -28,6 +30,7 @@ import { truncateTitle } from 'app/features/plugins/extensions/utils';
|
||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||
import { store } from 'app/store/store';
|
||||
|
||||
import { getCreateAlertInMenuAvailability } from '../../alerting/unified/utils/access-control';
|
||||
import { navigateToExplore } from '../../explore/state/main';
|
||||
import { getTimeSrv } from '../services/TimeSrv';
|
||||
|
||||
@ -202,8 +205,27 @@ export function getPanelMenu(
|
||||
subMenu: inspectMenu,
|
||||
});
|
||||
|
||||
const createAlert = async () => {
|
||||
const formValues = await panelToRuleFormValues(panel, dashboard);
|
||||
|
||||
const ruleFormUrl = urlUtil.renderUrl('/alerting/new', {
|
||||
defaults: JSON.stringify(formValues),
|
||||
returnTo: location.pathname + location.search,
|
||||
});
|
||||
|
||||
locationService.push(ruleFormUrl);
|
||||
};
|
||||
|
||||
const onCreateAlert = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
createAlert();
|
||||
reportInteraction('dashboards_panelheader_menu', { item: 'create-alert' });
|
||||
};
|
||||
|
||||
const subMenu: PanelMenuItem[] = [];
|
||||
const canEdit = dashboard.canEditPanel(panel);
|
||||
const isCreateAlertMenuOptionAvailable = getCreateAlertInMenuAvailability();
|
||||
|
||||
if (!(panel.isViewing || panel.isEditing)) {
|
||||
if (canEdit) {
|
||||
subMenu.push({
|
||||
@ -237,6 +259,13 @@ export function getPanelMenu(
|
||||
}
|
||||
}
|
||||
|
||||
if (isCreateAlertMenuOptionAvailable) {
|
||||
subMenu.push({
|
||||
text: t('panel.header-menu.create-alert', `Create alert`),
|
||||
onClick: onCreateAlert,
|
||||
});
|
||||
}
|
||||
|
||||
// add old angular panel options
|
||||
if (angularComponent) {
|
||||
const scope = angularComponent.getScope();
|
||||
@ -273,6 +302,12 @@ export function getPanelMenu(
|
||||
// When editing hide most actions
|
||||
if (panel.isEditing) {
|
||||
subMenu.length = 0;
|
||||
if (isCreateAlertMenuOptionAvailable) {
|
||||
subMenu.push({
|
||||
text: t('panel.header-menu.create-alert', `Create alert`),
|
||||
onClick: onCreateAlert,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (canEdit && panel.plugin && !panel.plugin.meta.skipDataQuery) {
|
||||
|
@ -773,6 +773,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "Kopieren",
|
||||
"create-alert": "",
|
||||
"create-library-panel": "Bibliotheksleiste erstellen",
|
||||
"duplicate": "Duplikat",
|
||||
"edit": "Bearbeiten",
|
||||
|
@ -773,6 +773,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "Copy",
|
||||
"create-alert": "Create alert",
|
||||
"create-library-panel": "Create library panel",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Edit",
|
||||
|
@ -779,6 +779,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "Copiar",
|
||||
"create-alert": "",
|
||||
"create-library-panel": "Crear panel de librería",
|
||||
"duplicate": "Duplicar",
|
||||
"edit": "Editar",
|
||||
|
@ -779,6 +779,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "Copier",
|
||||
"create-alert": "",
|
||||
"create-library-panel": "Créer un panneau Bibliothèque",
|
||||
"duplicate": "Dupliquer",
|
||||
"edit": "Modifier",
|
||||
|
@ -773,6 +773,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "Cőpy",
|
||||
"create-alert": "Cřęäŧę äľęřŧ",
|
||||
"create-library-panel": "Cřęäŧę ľįþřäřy päʼnęľ",
|
||||
"duplicate": "Đūpľįčäŧę",
|
||||
"edit": "Ēđįŧ",
|
||||
|
@ -767,6 +767,7 @@
|
||||
"panel": {
|
||||
"header-menu": {
|
||||
"copy": "复制",
|
||||
"create-alert": "",
|
||||
"create-library-panel": "创建库面板",
|
||||
"duplicate": "复制",
|
||||
"edit": "编辑",
|
||||
|
Loading…
Reference in New Issue
Block a user