Skip to content

Commit d214984

Browse files
committed
[Alerting] Fixing broken Alerts view when no Global All Kibana privilege (#88727)
* Making kibanaFeatures an optional parameter and catching error on plugin start * Gracefully handle 404 errors when no access to features endpoint * Adding functional test # Conflicts: # x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
1 parent 87293dc commit d214984

File tree

3 files changed

+118
-73
lines changed

3 files changed

+118
-73
lines changed

x-pack/plugins/triggers_actions_ui/public/plugin.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CoreSetup, CoreStart, Plugin as CorePlugin } from 'src/core/public';
99
import { i18n } from '@kbn/i18n';
1010
import { ReactElement } from 'react';
1111
import { FeaturesPluginStart } from '../../features/public';
12+
import { KibanaFeature } from '../../features/common';
1213
import { registerBuiltInActionTypes } from './application/components/builtin_action_types';
1314
import { ActionTypeModel, AlertTypeModel } from './types';
1415
import { TypeRegistry } from './application/type_registry';
@@ -122,7 +123,17 @@ export class Plugin
122123
];
123124

124125
const { renderApp } = await import('./application/app');
125-
const kibanaFeatures = await pluginsStart.features.getFeatures();
126+
127+
// The `/api/features` endpoint requires the "Global All" Kibana privilege. Users with a
128+
// subset of this privilege are not authorized to access this endpoint and will receive a 404
129+
// error that causes the Alerting view to fail to load.
130+
let kibanaFeatures: KibanaFeature[];
131+
try {
132+
kibanaFeatures = await pluginsStart.features.getFeatures();
133+
} catch (err) {
134+
kibanaFeatures = [];
135+
}
136+
126137
return renderApp({
127138
...coreStart,
128139
data: pluginsStart.data,

x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts

Lines changed: 90 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,91 +9,109 @@ import { FtrProviderContext } from '../../ftr_provider_context';
99

1010
export default ({ getPageObjects, getService }: FtrProviderContext) => {
1111
const testSubjects = getService('testSubjects');
12+
const security = getService('security');
1213
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
1314
const log = getService('log');
1415
const browser = getService('browser');
1516
const alerting = getService('alerting');
1617

1718
describe('Home page', function () {
18-
before(async () => {
19-
await pageObjects.common.navigateToApp('triggersActions');
20-
});
21-
22-
it('Loads the app', async () => {
23-
await log.debug('Checking for section heading to say Triggers and Actions.');
24-
25-
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
26-
expect(headingText).to.be('Alerts and Actions');
27-
});
28-
29-
describe('Connectors tab', () => {
30-
it('renders the connectors tab', async () => {
31-
// Navigate to the connectors tab
32-
await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
33-
34-
await pageObjects.header.waitUntilLoadingHasFinished();
35-
36-
// Verify url
37-
const url = await browser.getCurrentUrl();
38-
expect(url).to.contain(`/connectors`);
19+
describe('Loads the app with limited privileges', () => {
20+
before(async () => {
21+
await security.testUser.setRoles(['alerts_and_actions_role'], true);
22+
});
23+
after(async () => {
24+
await security.testUser.restoreDefaults();
25+
});
3926

40-
// Verify content
41-
await testSubjects.existOrFail('actionsList');
27+
it('Loads the Alerts page', async () => {
28+
await pageObjects.common.navigateToApp('triggersActions');
29+
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
30+
expect(headingText).to.be('Alerts and Actions');
4231
});
4332
});
4433

45-
describe('Alerts tab', () => {
46-
it('renders the alerts tab', async () => {
47-
// Navigate to the alerts tab
48-
await pageObjects.triggersActionsUI.changeTabs('alertsTab');
49-
50-
await pageObjects.header.waitUntilLoadingHasFinished();
51-
52-
// Verify url
53-
const url = await browser.getCurrentUrl();
54-
expect(url).to.contain(`/alerts`);
55-
56-
// Verify content
57-
await testSubjects.existOrFail('alertsList');
34+
describe('Loads the app', () => {
35+
before(async () => {
36+
await pageObjects.common.navigateToApp('triggersActions');
5837
});
59-
60-
it('navigates to an alert details page', async () => {
61-
const action = await alerting.actions.createAction({
62-
name: `Slack-${Date.now()}`,
63-
actionTypeId: '.slack',
64-
config: {},
65-
secrets: {
66-
webhookUrl: 'https://test',
67-
},
38+
39+
it('Loads the app', async () => {
40+
await log.debug('Checking for section heading to say Triggers and Actions.');
41+
42+
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
43+
expect(headingText).to.be('Alerts and Actions');
44+
});
45+
46+
describe('Connectors tab', () => {
47+
it('renders the connectors tab', async () => {
48+
// Navigate to the connectors tab
49+
await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
50+
51+
await pageObjects.header.waitUntilLoadingHasFinished();
52+
53+
// Verify url
54+
const url = await browser.getCurrentUrl();
55+
expect(url).to.contain(`/connectors`);
56+
57+
// Verify content
58+
await testSubjects.existOrFail('actionsList');
6859
});
69-
70-
const alert = await alerting.alerts.createAlwaysFiringWithAction(
71-
`test-alert-${Date.now()}`,
72-
{
73-
id: action.id,
74-
group: 'default',
75-
params: {
76-
message: 'from alert 1s',
77-
level: 'warn',
60+
});
61+
62+
describe('Alerts tab', () => {
63+
it('renders the alerts tab', async () => {
64+
// Navigate to the alerts tab
65+
await pageObjects.triggersActionsUI.changeTabs('alertsTab');
66+
67+
await pageObjects.header.waitUntilLoadingHasFinished();
68+
69+
// Verify url
70+
const url = await browser.getCurrentUrl();
71+
expect(url).to.contain(`/alerts`);
72+
73+
// Verify content
74+
await testSubjects.existOrFail('alertsList');
75+
});
76+
77+
it('navigates to an alert details page', async () => {
78+
const action = await alerting.actions.createAction({
79+
name: `Slack-${Date.now()}`,
80+
actionTypeId: '.slack',
81+
config: {},
82+
secrets: {
83+
webhookUrl: 'https://test',
7884
},
79-
}
80-
);
81-
82-
// refresh to see alert
83-
await browser.refresh();
84-
85-
await pageObjects.header.waitUntilLoadingHasFinished();
86-
87-
// Verify content
88-
await testSubjects.existOrFail('alertsList');
89-
90-
// click on first alert
91-
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
92-
93-
// Verify url
94-
expect(await browser.getCurrentUrl()).to.contain(`/alert/${alert.id}`);
95-
96-
await alerting.alerts.deleteAlert(alert.id);
85+
});
86+
87+
const alert = await alerting.alerts.createAlwaysFiringWithAction(
88+
`test-alert-${Date.now()}`,
89+
{
90+
id: action.id,
91+
group: 'default',
92+
params: {
93+
message: 'from alert 1s',
94+
level: 'warn',
95+
},
96+
}
97+
);
98+
99+
// refresh to see alert
100+
await browser.refresh();
101+
102+
await pageObjects.header.waitUntilLoadingHasFinished();
103+
104+
// Verify content
105+
await testSubjects.existOrFail('alertsList');
106+
107+
// click on first alert
108+
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
109+
110+
// Verify url
111+
expect(await browser.getCurrentUrl()).to.contain(`/alert/${alert.id}`);
112+
113+
await alerting.alerts.deleteAlert(alert.id);
114+
});
97115
});
98116
});
99117
});

x-pack/test/functional_with_es_ssl/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
8888
})}`,
8989
],
9090
},
91+
security: {
92+
roles: {
93+
alerts_and_actions_role: {
94+
kibana: [
95+
{
96+
feature: {
97+
actions: ['all'],
98+
stackAlerts: ['all'],
99+
},
100+
spaces: ['*'],
101+
},
102+
],
103+
},
104+
},
105+
defaultRoles: ['superuser'],
106+
},
91107
};
92108

93109
return returnedObject;

0 commit comments

Comments
 (0)