Introduce reserved ml privilege for the apm_user role (#72266)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
46fb8475f3
commit
09b11b61f0
|
@ -17,12 +17,12 @@ export const METRICS_FEATURE = {
|
||||||
order: 700,
|
order: 700,
|
||||||
icon: 'metricsApp',
|
icon: 'metricsApp',
|
||||||
navLinkId: 'metrics',
|
navLinkId: 'metrics',
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'metrics', 'kibana'],
|
||||||
catalogue: ['infraops'],
|
catalogue: ['infraops'],
|
||||||
alerting: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID],
|
alerting: [METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID],
|
||||||
privileges: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'metrics', 'kibana'],
|
||||||
catalogue: ['infraops'],
|
catalogue: ['infraops'],
|
||||||
api: ['infra'],
|
api: ['infra'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
|
@ -35,7 +35,7 @@ export const METRICS_FEATURE = {
|
||||||
ui: ['show', 'configureSource', 'save', 'alerting:show'],
|
ui: ['show', 'configureSource', 'save', 'alerting:show'],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'metrics', 'kibana'],
|
||||||
catalogue: ['infraops'],
|
catalogue: ['infraops'],
|
||||||
api: ['infra'],
|
api: ['infra'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
|
@ -58,12 +58,12 @@ export const LOGS_FEATURE = {
|
||||||
order: 800,
|
order: 800,
|
||||||
icon: 'logsApp',
|
icon: 'logsApp',
|
||||||
navLinkId: 'logs',
|
navLinkId: 'logs',
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'logs', 'kibana'],
|
||||||
catalogue: ['infralogging'],
|
catalogue: ['infralogging'],
|
||||||
alerting: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID],
|
alerting: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID],
|
||||||
privileges: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'logs', 'kibana'],
|
||||||
catalogue: ['infralogging'],
|
catalogue: ['infralogging'],
|
||||||
api: ['infra'],
|
api: ['infra'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
|
@ -76,7 +76,7 @@ export const LOGS_FEATURE = {
|
||||||
ui: ['show', 'configureSource', 'save'],
|
ui: ['show', 'configureSource', 'save'],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
app: ['infra', 'kibana'],
|
app: ['infra', 'logs', 'kibana'],
|
||||||
catalogue: ['infralogging'],
|
catalogue: ['infralogging'],
|
||||||
api: ['infra'],
|
api: ['infra'],
|
||||||
alerting: {
|
alerting: {
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
import { KibanaRequest } from 'kibana/server';
|
import { KibanaRequest } from 'kibana/server';
|
||||||
import { PLUGIN_ID } from '../constants/app';
|
import { PLUGIN_ID } from '../constants/app';
|
||||||
|
|
||||||
|
export const apmUserMlCapabilities = {
|
||||||
|
canGetJobs: false,
|
||||||
|
};
|
||||||
|
|
||||||
export const userMlCapabilities = {
|
export const userMlCapabilities = {
|
||||||
canAccessML: false,
|
canAccessML: false,
|
||||||
// Anomaly Detection
|
// Anomaly Detection
|
||||||
|
@ -68,6 +72,7 @@ export function getDefaultCapabilities(): MlCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPluginPrivileges() {
|
export function getPluginPrivileges() {
|
||||||
|
const apmUserMlCapabilitiesKeys = Object.keys(apmUserMlCapabilities);
|
||||||
const userMlCapabilitiesKeys = Object.keys(userMlCapabilities);
|
const userMlCapabilitiesKeys = Object.keys(userMlCapabilities);
|
||||||
const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities);
|
const adminMlCapabilitiesKeys = Object.keys(adminMlCapabilities);
|
||||||
const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys];
|
const allMlCapabilitiesKeys = [...adminMlCapabilitiesKeys, ...userMlCapabilitiesKeys];
|
||||||
|
@ -101,6 +106,17 @@ export function getPluginPrivileges() {
|
||||||
read: savedObjects,
|
read: savedObjects,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
apmUser: {
|
||||||
|
excludeFromBasePrivileges: true,
|
||||||
|
app: [],
|
||||||
|
catalogue: [],
|
||||||
|
savedObject: {
|
||||||
|
all: [],
|
||||||
|
read: [],
|
||||||
|
},
|
||||||
|
api: apmUserMlCapabilitiesKeys.map((k) => `ml:${k}`),
|
||||||
|
ui: apmUserMlCapabilitiesKeys,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup(coreSetup: CoreSetup, plugins: PluginsSetup): MlPluginSetup {
|
public setup(coreSetup: CoreSetup, plugins: PluginsSetup): MlPluginSetup {
|
||||||
const { admin, user } = getPluginPrivileges();
|
const { admin, user, apmUser } = getPluginPrivileges();
|
||||||
|
|
||||||
plugins.features.registerFeature({
|
plugins.features.registerFeature({
|
||||||
id: PLUGIN_ID,
|
id: PLUGIN_ID,
|
||||||
|
@ -108,6 +108,10 @@ export class MlServerPlugin implements Plugin<MlPluginSetup, MlPluginStart, Plug
|
||||||
id: 'ml_admin',
|
id: 'ml_admin',
|
||||||
privilege: admin,
|
privilege: admin,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'ml_apm_user',
|
||||||
|
privilege: apmUser,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,7 +53,7 @@ describe('usingPrivileges', () => {
|
||||||
new Feature({
|
new Feature({
|
||||||
id: 'fooFeature',
|
id: 'fooFeature',
|
||||||
name: 'Foo Feature',
|
name: 'Foo Feature',
|
||||||
app: ['fooApp'],
|
app: ['fooApp', 'foo'],
|
||||||
navLinkId: 'foo',
|
navLinkId: 'foo',
|
||||||
privileges: null,
|
privileges: null,
|
||||||
}),
|
}),
|
||||||
|
@ -129,7 +129,7 @@ describe('usingPrivileges', () => {
|
||||||
new Feature({
|
new Feature({
|
||||||
id: 'fooFeature',
|
id: 'fooFeature',
|
||||||
name: 'Foo Feature',
|
name: 'Foo Feature',
|
||||||
app: [],
|
app: ['foo'],
|
||||||
navLinkId: 'foo',
|
navLinkId: 'foo',
|
||||||
privileges: null,
|
privileges: null,
|
||||||
}),
|
}),
|
||||||
|
@ -262,7 +262,7 @@ describe('usingPrivileges', () => {
|
||||||
id: 'barFeature',
|
id: 'barFeature',
|
||||||
name: 'Bar Feature',
|
name: 'Bar Feature',
|
||||||
navLinkId: 'bar',
|
navLinkId: 'bar',
|
||||||
app: [],
|
app: ['bar'],
|
||||||
privileges: null,
|
privileges: null,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -412,7 +412,7 @@ describe('all', () => {
|
||||||
new Feature({
|
new Feature({
|
||||||
id: 'fooFeature',
|
id: 'fooFeature',
|
||||||
name: 'Foo Feature',
|
name: 'Foo Feature',
|
||||||
app: [],
|
app: ['foo'],
|
||||||
navLinkId: 'foo',
|
navLinkId: 'foo',
|
||||||
privileges: null,
|
privileges: null,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -18,12 +18,11 @@ export function disableUICapabilitiesFactory(
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
authz: AuthorizationServiceSetup
|
authz: AuthorizationServiceSetup
|
||||||
) {
|
) {
|
||||||
// nav links are sourced from two places:
|
// nav links are sourced from the apps property.
|
||||||
// 1) The `navLinkId` property. This is deprecated and will be removed (https://github.com/elastic/kibana/issues/66217)
|
// The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship.
|
||||||
// 2) The apps property. The Kibana Platform associates nav links to the app which registers it, in a 1:1 relationship.
|
// This behavior is replacing the `navLinkId` property.
|
||||||
// This behavior is replacing the `navLinkId` property above.
|
|
||||||
const featureNavLinkIds = features
|
const featureNavLinkIds = features
|
||||||
.flatMap((feature) => [feature.navLinkId, ...feature.app])
|
.flatMap((feature) => feature.app)
|
||||||
.filter((navLinkId) => navLinkId != null);
|
.filter((navLinkId) => navLinkId != null);
|
||||||
|
|
||||||
const shouldDisableFeatureUICapability = (
|
const shouldDisableFeatureUICapability = (
|
||||||
|
|
|
@ -9,9 +9,6 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder';
|
||||||
|
|
||||||
export class FeaturePrivilegeNavlinkBuilder extends BaseFeaturePrivilegeBuilder {
|
export class FeaturePrivilegeNavlinkBuilder extends BaseFeaturePrivilegeBuilder {
|
||||||
public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] {
|
public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] {
|
||||||
const appNavLinks = feature.app.map((app) => this.actions.ui.get('navLinks', app));
|
return (privilegeDefinition.app ?? []).map((app) => this.actions.ui.get('navLinks', app));
|
||||||
return feature.navLinkId
|
|
||||||
? [this.actions.ui.get('navLinks', feature.navLinkId), ...appNavLinks]
|
|
||||||
: appNavLinks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,20 +54,8 @@ describe('features', () => {
|
||||||
|
|
||||||
const actual = privileges.get();
|
const actual = privileges.get();
|
||||||
expect(actual).toHaveProperty('features.foo-feature', {
|
expect(actual).toHaveProperty('features.foo-feature', {
|
||||||
all: [
|
all: [actions.login, actions.version],
|
||||||
actions.login,
|
read: [actions.login, actions.version],
|
||||||
actions.version,
|
|
||||||
actions.ui.get('navLinks', 'kibana:foo'),
|
|
||||||
actions.ui.get('navLinks', 'app-1'),
|
|
||||||
actions.ui.get('navLinks', 'app-2'),
|
|
||||||
],
|
|
||||||
read: [
|
|
||||||
actions.login,
|
|
||||||
actions.version,
|
|
||||||
actions.ui.get('navLinks', 'kibana:foo'),
|
|
||||||
actions.ui.get('navLinks', 'app-1'),
|
|
||||||
actions.ui.get('navLinks', 'app-2'),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -275,7 +263,6 @@ describe('features', () => {
|
||||||
actions.ui.get('catalogue', 'all-catalogue-2'),
|
actions.ui.get('catalogue', 'all-catalogue-2'),
|
||||||
actions.ui.get('management', 'all-management', 'all-management-1'),
|
actions.ui.get('management', 'all-management', 'all-management-1'),
|
||||||
actions.ui.get('management', 'all-management', 'all-management-2'),
|
actions.ui.get('management', 'all-management', 'all-management-2'),
|
||||||
actions.ui.get('navLinks', 'kibana:foo'),
|
|
||||||
actions.savedObject.get('all-savedObject-all-1', 'bulk_get'),
|
actions.savedObject.get('all-savedObject-all-1', 'bulk_get'),
|
||||||
actions.savedObject.get('all-savedObject-all-1', 'get'),
|
actions.savedObject.get('all-savedObject-all-1', 'get'),
|
||||||
actions.savedObject.get('all-savedObject-all-1', 'find'),
|
actions.savedObject.get('all-savedObject-all-1', 'find'),
|
||||||
|
@ -386,7 +373,6 @@ describe('features', () => {
|
||||||
actions.ui.get('catalogue', 'read-catalogue-2'),
|
actions.ui.get('catalogue', 'read-catalogue-2'),
|
||||||
actions.ui.get('management', 'read-management', 'read-management-1'),
|
actions.ui.get('management', 'read-management', 'read-management-1'),
|
||||||
actions.ui.get('management', 'read-management', 'read-management-2'),
|
actions.ui.get('management', 'read-management', 'read-management-2'),
|
||||||
actions.ui.get('navLinks', 'kibana:foo'),
|
|
||||||
actions.savedObject.get('read-savedObject-all-1', 'bulk_get'),
|
actions.savedObject.get('read-savedObject-all-1', 'bulk_get'),
|
||||||
actions.savedObject.get('read-savedObject-all-1', 'get'),
|
actions.savedObject.get('read-savedObject-all-1', 'get'),
|
||||||
actions.savedObject.get('read-savedObject-all-1', 'find'),
|
actions.savedObject.get('read-savedObject-all-1', 'find'),
|
||||||
|
@ -644,12 +630,7 @@ describe('reserved', () => {
|
||||||
const privileges = privilegesFactory(actions, mockXPackMainPlugin as any, mockLicenseService);
|
const privileges = privilegesFactory(actions, mockXPackMainPlugin as any, mockLicenseService);
|
||||||
|
|
||||||
const actual = privileges.get();
|
const actual = privileges.get();
|
||||||
expect(actual).toHaveProperty('reserved.foo', [
|
expect(actual).toHaveProperty('reserved.foo', [actions.version]);
|
||||||
actions.version,
|
|
||||||
actions.ui.get('navLinks', 'kibana:foo'),
|
|
||||||
actions.ui.get('navLinks', 'app-1'),
|
|
||||||
actions.ui.get('navLinks', 'app-2'),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`actions only specified at the privilege are alright too`, () => {
|
test(`actions only specified at the privilege are alright too`, () => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ const features = ([
|
||||||
id: 'feature_2',
|
id: 'feature_2',
|
||||||
name: 'Feature 2',
|
name: 'Feature 2',
|
||||||
navLinkId: 'feature2',
|
navLinkId: 'feature2',
|
||||||
app: [],
|
app: ['feature2'],
|
||||||
catalogue: ['feature2Entry'],
|
catalogue: ['feature2Entry'],
|
||||||
management: {
|
management: {
|
||||||
kibana: ['somethingElse'],
|
kibana: ['somethingElse'],
|
||||||
|
|
|
@ -83,8 +83,7 @@ function toggleDisabledFeatures(
|
||||||
|
|
||||||
for (const feature of disabledFeatures) {
|
for (const feature of disabledFeatures) {
|
||||||
// Disable associated navLink, if one exists
|
// Disable associated navLink, if one exists
|
||||||
const featureNavLinks = feature.navLinkId ? [feature.navLinkId, ...feature.app] : feature.app;
|
feature.app.forEach((app) => {
|
||||||
featureNavLinks.forEach((app) => {
|
|
||||||
if (navLinks.hasOwnProperty(app) && !enabledAppEntries.has(app)) {
|
if (navLinks.hasOwnProperty(app) && !enabledAppEntries.has(app)) {
|
||||||
navLinks[app] = false;
|
navLinks[app] = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
},
|
},
|
||||||
global: ['all', 'read'],
|
global: ['all', 'read'],
|
||||||
space: ['all', 'read'],
|
space: ['all', 'read'],
|
||||||
reserved: ['ml_user', 'ml_admin', 'monitoring'],
|
reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'],
|
||||||
};
|
};
|
||||||
|
|
||||||
await supertest
|
await supertest
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
},
|
},
|
||||||
global: ['all', 'read'],
|
global: ['all', 'read'],
|
||||||
space: ['all', 'read'],
|
space: ['all', 'read'],
|
||||||
reserved: ['ml_user', 'ml_admin', 'monitoring'],
|
reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'],
|
||||||
};
|
};
|
||||||
|
|
||||||
await supertest
|
await supertest
|
||||||
|
|
|
@ -423,19 +423,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
expect(navLinks).to.not.contain(['Metrics']);
|
expect(navLinks).to.not.contain(['Metrics']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => {
|
it(`metrics app is inaccessible and returns a 404`, async () => {
|
||||||
await PageObjects.common.navigateToApp('infraOps');
|
await PageObjects.common.navigateToActualUrl('infraOps', '', {
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
await PageObjects.common.navigateToUrlWithBrowserHistory(
|
|
||||||
'infraOps',
|
|
||||||
'/inventory',
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
ensureCurrentUrl: false,
|
ensureCurrentUrl: false,
|
||||||
shouldLoginIfPrompted: false,
|
shouldLoginIfPrompted: false,
|
||||||
}
|
});
|
||||||
|
const messageText = await PageObjects.common.getBodyText();
|
||||||
|
expect(messageText).to.eql(
|
||||||
|
JSON.stringify({
|
||||||
|
statusCode: 404,
|
||||||
|
error: 'Not Found',
|
||||||
|
message: 'Not Found',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -79,21 +79,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => {
|
it(`metrics app is inaccessible and Application Not Found message is rendered`, async () => {
|
||||||
await PageObjects.common.navigateToApp('infraOps', {
|
await PageObjects.common.navigateToActualUrl('infraOps', '', {
|
||||||
basePath: '/s/custom_space',
|
|
||||||
});
|
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
await PageObjects.common.navigateToUrlWithBrowserHistory(
|
|
||||||
'infraOps',
|
|
||||||
'/inventory',
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
basePath: '/s/custom_space',
|
|
||||||
ensureCurrentUrl: false,
|
ensureCurrentUrl: false,
|
||||||
shouldLoginIfPrompted: false,
|
shouldLoginIfPrompted: false,
|
||||||
}
|
basePath: '/s/custom_space',
|
||||||
|
});
|
||||||
|
const messageText = await PageObjects.common.getBodyText();
|
||||||
|
expect(messageText).to.eql(
|
||||||
|
JSON.stringify({
|
||||||
|
statusCode: 404,
|
||||||
|
error: 'Not Found',
|
||||||
|
message: 'Not Found',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -187,19 +187,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
expect(navLinks).to.not.contain('Logs');
|
expect(navLinks).to.not.contain('Logs');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`logs app is inaccessible and Application Not Found message is rendered`, async () => {
|
it(`logs app is inaccessible and returns a 404`, async () => {
|
||||||
await PageObjects.common.navigateToApp('infraLogs');
|
await PageObjects.common.navigateToActualUrl('infraLogs', '', {
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
await PageObjects.common.navigateToUrlWithBrowserHistory(
|
|
||||||
'infraLogs',
|
|
||||||
'/stream',
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
ensureCurrentUrl: false,
|
ensureCurrentUrl: false,
|
||||||
shouldLoginIfPrompted: false,
|
shouldLoginIfPrompted: false,
|
||||||
}
|
});
|
||||||
|
const messageText = await PageObjects.common.getBodyText();
|
||||||
|
expect(messageText).to.eql(
|
||||||
|
JSON.stringify({
|
||||||
|
statusCode: 404,
|
||||||
|
error: 'Not Found',
|
||||||
|
message: 'Not Found',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -80,21 +80,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`logs app is inaccessible and Application Not Found message is rendered`, async () => {
|
it(`logs app is inaccessible and Application Not Found message is rendered`, async () => {
|
||||||
await PageObjects.common.navigateToApp('infraLogs', {
|
await PageObjects.common.navigateToActualUrl('infraLogs', '', {
|
||||||
basePath: '/s/custom_space',
|
|
||||||
});
|
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
await PageObjects.common.navigateToUrlWithBrowserHistory(
|
|
||||||
'infraLogs',
|
|
||||||
'/stream',
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
basePath: '/s/custom_space',
|
|
||||||
ensureCurrentUrl: false,
|
ensureCurrentUrl: false,
|
||||||
shouldLoginIfPrompted: false,
|
shouldLoginIfPrompted: false,
|
||||||
}
|
basePath: '/s/custom_space',
|
||||||
|
});
|
||||||
|
const messageText = await PageObjects.common.getBodyText();
|
||||||
|
expect(messageText).to.eql(
|
||||||
|
JSON.stringify({
|
||||||
|
statusCode: 404,
|
||||||
|
error: 'Not Found',
|
||||||
|
message: 'Not Found',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
await testSubjects.existOrFail('~appNotFoundPageContent');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Feature {
|
interface Feature {
|
||||||
navLinkId: string;
|
app: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Features {
|
export interface Features {
|
||||||
|
|
|
@ -19,11 +19,11 @@ class FooPlugin implements Plugin {
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
icon: 'upArrow',
|
icon: 'upArrow',
|
||||||
navLinkId: 'foo_plugin',
|
navLinkId: 'foo_plugin',
|
||||||
app: ['kibana'],
|
app: ['foo_plugin', 'kibana'],
|
||||||
catalogue: ['foo'],
|
catalogue: ['foo'],
|
||||||
privileges: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
app: ['kibana'],
|
app: ['foo_plugin', 'kibana'],
|
||||||
catalogue: ['foo'],
|
catalogue: ['foo'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
all: ['foo'],
|
all: ['foo'],
|
||||||
|
@ -32,7 +32,7 @@ class FooPlugin implements Plugin {
|
||||||
ui: ['create', 'edit', 'delete', 'show'],
|
ui: ['create', 'edit', 'delete', 'show'],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
app: ['kibana'],
|
app: ['foo_plugin', 'kibana'],
|
||||||
catalogue: ['foo'],
|
catalogue: ['foo'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
all: [],
|
all: [],
|
||||||
|
|
|
@ -13,11 +13,14 @@ export class NavLinksBuilder {
|
||||||
...features,
|
...features,
|
||||||
// management isn't a first-class "feature", but it makes our life easier here to pretend like it is
|
// management isn't a first-class "feature", but it makes our life easier here to pretend like it is
|
||||||
management: {
|
management: {
|
||||||
navLinkId: 'kibana:stack_management',
|
app: ['kibana:stack_management'],
|
||||||
},
|
},
|
||||||
// TODO: Temp until navLinkIds fix is merged in
|
// TODO: Temp until navLinkIds fix is merged in
|
||||||
appSearch: {
|
appSearch: {
|
||||||
navLinkId: 'appSearch',
|
app: ['appSearch', 'workplaceSearch'],
|
||||||
|
},
|
||||||
|
kibana: {
|
||||||
|
app: ['kibana'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -38,9 +41,9 @@ export class NavLinksBuilder {
|
||||||
private build(callback: buildCallback): Record<string, boolean> {
|
private build(callback: buildCallback): Record<string, boolean> {
|
||||||
const navLinks = {} as Record<string, boolean>;
|
const navLinks = {} as Record<string, boolean>;
|
||||||
for (const [featureId, feature] of Object.entries(this.features)) {
|
for (const [featureId, feature] of Object.entries(this.features)) {
|
||||||
if (feature.navLinkId) {
|
feature.app.forEach((app) => {
|
||||||
navLinks[feature.navLinkId] = callback(featureId);
|
navLinks[app] = callback(featureId);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return navLinks;
|
return navLinks;
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class FeaturesService {
|
||||||
(acc: Features, feature: any) => ({
|
(acc: Features, feature: any) => ({
|
||||||
...acc,
|
...acc,
|
||||||
[feature.id]: {
|
[feature.id]: {
|
||||||
navLinkId: feature.navLinkId,
|
app: feature.app,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class UICapabilitiesService {
|
||||||
}): Promise<GetUICapabilitiesResult> {
|
}): Promise<GetUICapabilitiesResult> {
|
||||||
const features = await this.featureService.get();
|
const features = await this.featureService.get();
|
||||||
const applications = Object.values(features)
|
const applications = Object.values(features)
|
||||||
.map((feature) => feature.navLinkId)
|
.flatMap((feature) => feature.app)
|
||||||
.filter((link) => !!link);
|
.filter((link) => !!link);
|
||||||
|
|
||||||
const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : '';
|
const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : '';
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
|
||||||
expect(uiCapabilities.success).to.be(true);
|
expect(uiCapabilities.success).to.be(true);
|
||||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||||
navLinksBuilder.only('management', 'foo')
|
navLinksBuilder.only('management', 'foo', 'kibana')
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'legacy_all':
|
case 'legacy_all':
|
||||||
|
|
Loading…
Reference in a new issue