[Guided onboarding] Use Kibana features to grant access (#155065)

## Summary

Fixes https://github.com/elastic/kibana/issues/149132

This PR adds a Kibana feature for the guided onboarding plugin for
better permissions handling. By default `kibana_admin` and `editor`
roles are granted access to guided onboarding. The role `viewer` on the
other hand doesn't have enough permissions to see or use guided
onboarding. For any roles that don't have the correct permissions,
guided onboarding is completely disabled, the same as it's disabled
on-prem.
When creating a new role, the feature "Setup guides" can be enabled or
disabled.

### How to test
1. Add `xpack.cloud.id: 'testID'` to `/config/kibana.dev.yml`
1. Start ES with `yarn es snapshot` and Kibana with `yarn start``
2. Login as elastic and create a test user with the role `viewer`
3. Clear everything from your browser's local storage 
4. Login as the test user and check the following
- On the first visit, the "on-prem" welcome message is shown (not the
guided onboarding landing page)
- The url `/app/home#/getting_started` is unknown and redirects back to
the home page
- There is no button "Setup guides" in the header
- There is no link "Setup guides" in the help menu

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yulia Čech 2023-04-26 13:33:58 +02:00 committed by GitHub
parent 207c23e2da
commit b75546f7eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 230 additions and 82 deletions

View file

@ -11,13 +11,7 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { Router, Switch } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';
import {
EuiPage,
EuiPageBody,
EuiPageContent_Deprecated as EuiPageContent,
EuiPageHeader,
EuiTitle,
} from '@elastic/eui';
import { EuiPageTemplate } from '@elastic/eui';
import { CoreStart, ScopedHistory } from '@kbn/core/public';
@ -39,19 +33,17 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps
return (
<I18nProvider>
<EuiPage restrictWidth="1000px">
<EuiPageBody>
<EuiPageHeader>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="guidedOnboardingExample.title"
defaultMessage="Guided onboarding examples"
/>
</h1>
</EuiTitle>
</EuiPageHeader>
<EuiPageContent>
<EuiPageTemplate restrictWidth={true} panelled={true}>
<EuiPageTemplate.Header
pageTitle={
<FormattedMessage
id="guidedOnboardingExample.title"
defaultMessage="Guided onboarding examples"
/>
}
/>
{guidedOnboarding.guidedOnboardingApi?.isEnabled ? (
<EuiPageTemplate.Section>
<Router history={history}>
<Switch>
<Route exact path="/">
@ -75,9 +67,31 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps
/>
</Switch>
</Router>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</EuiPageTemplate.Section>
) : (
<EuiPageTemplate.EmptyPrompt
iconType="error"
color="danger"
title={
<h2>
<FormattedMessage
id="guidedOnboardingExample.errorTitle"
defaultMessage="Guided onboarding is disabled"
/>
</h2>
}
body={
<p>
<FormattedMessage
id="guidedOnboardingExample.errorDescription"
defaultMessage="Make sure your Kibana instance runs on Cloud and/or
your user has access to Setup guides feature."
/>
</p>
}
/>
)}
</EuiPageTemplate>
</I18nProvider>
);
};

View file

@ -9,4 +9,6 @@
export const PLUGIN_ID = 'guidedOnboarding';
export const PLUGIN_NAME = 'guidedOnboarding';
export const PLUGIN_FEATURE = 'guidedOnboardingFeature';
export const API_BASE_PATH = '/internal/guided_onboarding';

View file

@ -8,7 +8,8 @@
"server": true,
"browser": true,
"optionalPlugins": [
"cloud"
"cloud",
"features"
],
"requiredBundles": [
"kibanaReact"

View file

@ -28,6 +28,7 @@ const apiServiceMock: jest.Mocked<GuidedOnboardingPluginStart> = {
isGuidePanelOpen$: new BehaviorSubject(false),
isLoading$: new BehaviorSubject(false),
getGuideConfig: jest.fn(),
isEnabled: true,
},
};

View file

@ -21,6 +21,8 @@ import {
} from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { PLUGIN_FEATURE } from '../common/constants';
import type {
AppPluginStartDependencies,
GuidedOnboardingPluginSetup,
@ -43,11 +45,12 @@ export class GuidedOnboardingPlugin
): GuidedOnboardingPluginStart {
const { chrome, http, theme, application, notifications, uiSettings } = core;
// Guided onboarding UI is only available on cloud and if the access to the Kibana feature is granted
const isEnabled = !!(cloud?.isCloudEnabled && application.capabilities[PLUGIN_FEATURE].enabled);
// Initialize services
apiService.setup(http, !!cloud?.isCloudEnabled);
apiService.setup(http, isEnabled);
// Guided onboarding UI is only available on cloud
if (cloud?.isCloudEnabled) {
if (isEnabled) {
chrome.navControls.registerExtension({
order: 1000,
mount: (target) =>

View file

@ -41,15 +41,15 @@ import {
import { ConfigService } from './config.service';
export class ApiService implements GuidedOnboardingApi {
private isCloudEnabled: boolean | undefined;
private _isEnabled: boolean = false;
private client: HttpSetup | undefined;
private pluginState$!: BehaviorSubject<PluginState | undefined>;
public isLoading$ = new BehaviorSubject<boolean>(false);
public isGuidePanelOpen$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private configService = new ConfigService();
public setup(httpClient: HttpSetup, isCloudEnabled: boolean) {
this.isCloudEnabled = isCloudEnabled;
public setup(httpClient: HttpSetup, isEnabled: boolean) {
this._isEnabled = isEnabled;
this.client = httpClient;
this.pluginState$ = new BehaviorSubject<PluginState | undefined>(undefined);
this.isGuidePanelOpen$ = new BehaviorSubject<boolean>(false);
@ -94,7 +94,7 @@ export class ApiService implements GuidedOnboardingApi {
* Subsequently, the observable is updated automatically, when the state changes.
*/
public fetchPluginState$(): Observable<PluginState | undefined> {
if (!this.isCloudEnabled) {
if (!this._isEnabled) {
return of(undefined);
}
if (!this.client) {
@ -118,7 +118,7 @@ export class ApiService implements GuidedOnboardingApi {
* where all guides are displayed with their corresponding status.
*/
public async fetchAllGuidesState(): Promise<{ state: GuideState[] } | undefined> {
if (!this.isCloudEnabled) {
if (!this._isEnabled) {
return undefined;
}
if (!this.client) {
@ -143,7 +143,7 @@ export class ApiService implements GuidedOnboardingApi {
state: { status?: PluginStatus; guide?: GuideState },
panelState: boolean
): Promise<{ pluginState: PluginState } | undefined> {
if (!this.isCloudEnabled) {
if (!this._isEnabled) {
return undefined;
}
if (!this.client) {
@ -467,7 +467,7 @@ export class ApiService implements GuidedOnboardingApi {
* @return {Promise} a promise with the guide config or undefined if the config is not found
*/
public async getGuideConfig(guideId: GuideId): Promise<GuideConfig | undefined> {
if (!this.isCloudEnabled) {
if (!this._isEnabled) {
return undefined;
}
if (!this.client) {
@ -478,6 +478,10 @@ export class ApiService implements GuidedOnboardingApi {
this.isLoading$.next(false);
return config;
}
public get isEnabled() {
return this._isEnabled;
}
}
export const apiService = new ApiService();

View file

@ -62,4 +62,5 @@ export interface GuidedOnboardingApi {
isGuidePanelOpen$: Observable<boolean>;
isLoading$: Observable<boolean>;
getGuideConfig: (guideId: GuideId) => Promise<GuideConfig | undefined>;
readonly isEnabled: boolean;
}

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { PLUGIN_FEATURE, PLUGIN_ID } from '../common/constants';
import { guideStateSavedObjectsType, pluginStateSavedObjectsType } from './saved_objects';
export const GUIDED_ONBOARDING_FEATURE: KibanaFeatureConfig = {
id: PLUGIN_FEATURE,
name: i18n.translate('guidedOnboarding.featureRegistry.featureName', {
defaultMessage: 'Setup guides',
}),
category: DEFAULT_APP_CATEGORIES.management,
app: [PLUGIN_ID],
privileges: {
all: {
app: [PLUGIN_ID],
savedObject: {
all: [guideStateSavedObjectsType, pluginStateSavedObjectsType],
read: [],
},
ui: ['enabled'],
},
read: {
// we haven't implemented "read-only" access yet, so this feature can only be granted
// as "all" or "none"
disabled: true,
savedObject: {
all: [],
read: [],
},
ui: [],
},
},
};

View file

@ -9,6 +9,8 @@
import { PluginInitializerContext, CoreSetup, Plugin, Logger } from '@kbn/core/server';
import type { GuideId, GuideConfig } from '@kbn/guided-onboarding';
import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { GUIDED_ONBOARDING_FEATURE } from './feature';
import { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types';
import { defineRoutes } from './routes';
import { guideStateSavedObjects, pluginStateSavedObjects } from './saved_objects';
@ -25,7 +27,7 @@ export class GuidedOnboardingPlugin
this.guidesConfig = {} as GuidesConfig;
}
public setup(core: CoreSetup) {
public setup(core: CoreSetup, plugins: { features?: FeaturesPluginSetup }) {
this.logger.debug('guidedOnboarding: Setup');
const router = core.http.createRouter();
@ -36,6 +38,8 @@ export class GuidedOnboardingPlugin
core.savedObjects.registerType(guideStateSavedObjects);
core.savedObjects.registerType(pluginStateSavedObjects);
plugins.features?.registerKibanaFeature(GUIDED_ONBOARDING_FEATURE);
return {
registerGuideConfig: (guideId: GuideId, guideConfig: GuideConfig) => {
if (this.guidesConfig[guideId]) {

View file

@ -18,6 +18,7 @@
"@kbn/core-http-browser",
"@kbn/core-http-browser-mocks",
"@kbn/config-schema",
"@kbn/features-plugin",
],
"exclude": [
"target/**/*",

View file

@ -308,12 +308,19 @@ exports[`home isNewKibanaInstance should safely handle exceptions 1`] = `
Array [
"./home#/getting_started",
],
Array [
"./home#/getting_started",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
@ -335,12 +342,19 @@ exports[`home isNewKibanaInstance should safely handle exceptions 1`] = `
Array [
"./home#/getting_started",
],
Array [
"./home#/getting_started",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
@ -389,12 +403,19 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
Array [
"./home#/getting_started",
],
Array [
"./home#/getting_started",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
@ -416,12 +437,19 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
Array [
"./home#/getting_started",
],
Array [
"./home#/getting_started",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
@ -437,12 +465,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
</_KibanaPageTemplate>
`;
exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `
<Welcome
onSkip={[Function]}
urlBasePath="goober"
/>
`;
exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `""`;
exports[`home should render home component 1`] = `
<_KibanaPageTemplate

View file

@ -35,7 +35,7 @@ interface Props {
}
export const AddData: FC<Props> = ({ addBasePath, application, isDarkMode, isCloudEnabled }) => {
const { trackUiMetric } = getServices();
const { trackUiMetric, guidedOnboardingService } = getServices();
const canAccessIntegrations = application.capabilities.navLinks.integrations;
if (canAccessIntegrations) {
return (
@ -70,7 +70,7 @@ export const AddData: FC<Props> = ({ addBasePath, application, isDarkMode, isClo
<EuiSpacer />
<EuiFlexGroup gutterSize="m">
{isCloudEnabled && (
{guidedOnboardingService?.isEnabled && (
<EuiFlexItem grow={false}>
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiButton
@ -93,9 +93,9 @@ export const AddData: FC<Props> = ({ addBasePath, application, isDarkMode, isClo
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiButton
data-test-subj="homeAddData"
// on self managed this button is primary
// on Cloud this button is secondary, because there is a "guided onboarding" button
fill={!isCloudEnabled}
// when guided onboarding is disabled, this button is primary
// otherwise it's secondary, because there is a "guided onboarding" button
fill={!guidedOnboardingService?.isEnabled}
href={addBasePath('/app/integrations/browse')}
iconType="plusInCircle"
onClick={(event: MouseEvent) => {

View file

@ -15,6 +15,8 @@ import { Welcome } from './welcome';
let mockHasIntegrationsPermission = true;
const mockNavigateToUrl = jest.fn();
let mockIsEnabled = false;
jest.mock('../kibana_services', () => ({
getServices: () => ({
getBasePath: () => 'path',
@ -31,6 +33,9 @@ jest.mock('../kibana_services', () => ({
},
},
},
guidedOnboardingService: {
isEnabled: mockIsEnabled,
},
}),
}));
@ -234,7 +239,8 @@ describe('home', () => {
expect(component.find(Welcome).exists()).toBe(false);
});
test('should redirect to guided onboarding on Cloud instead of welcome screen', async () => {
test('should redirect to guided onboarding on Cloud instead of welcome screen if guided onboarding is enabled', async () => {
mockIsEnabled = true;
const isCloudEnabled = true;
const hasUserDataView = jest.fn(async () => false);

View file

@ -188,15 +188,14 @@ export class Home extends Component<HomeProps, State> {
public render() {
const { isLoading, isWelcomeEnabled, isNewKibanaInstance } = this.state;
const { isCloudEnabled } = this.props;
const { application } = getServices();
const { application, guidedOnboardingService } = getServices();
if (isWelcomeEnabled) {
if (isLoading) {
return this.renderLoading();
}
if (isNewKibanaInstance) {
if (isCloudEnabled) {
if (guidedOnboardingService?.isEnabled) {
application.navigateToUrl('./home#/getting_started');
return null;
}

View file

@ -29,6 +29,7 @@ export function HomeApp({ directories, solutions }) {
addBasePath,
environmentService,
dataViewsService,
guidedOnboardingService,
} = getServices();
const environment = environmentService.getEnvironment();
const isCloudEnabled = environment.cloud;
@ -69,9 +70,11 @@ export function HomeApp({ directories, solutions }) {
<Switch>
<Route path="/tutorial/:id" render={renderTutorial} />
<Route path="/tutorial_directory/:tab?" render={renderTutorialDirectory} />
<Route path="/getting_started">
<GettingStarted />
</Route>
{guidedOnboardingService.isEnabled && (
<Route path="/getting_started">
<GettingStarted />
</Route>
)}
<Route exact path="/">
<Home
addBasePath={addBasePath}

View file

@ -9,7 +9,8 @@
"browser": true,
"optionalPlugins": [
"cloud",
"security"
"security",
"guided_onboarding"
]
}
}

View file

@ -10,6 +10,7 @@ import { CloudLinksPlugin } from './plugin';
import { coreMock } from '@kbn/core/public/mocks';
import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
import { securityMock } from '@kbn/security-plugin/public/mocks';
import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks';
describe('Cloud Links Plugin - public', () => {
let plugin: CloudLinksPlugin;
@ -32,26 +33,46 @@ describe('Cloud Links Plugin - public', () => {
});
describe('Onboarding Setup Guide link registration', () => {
test('registers the Onboarding Setup Guide link when cloud is enabled and it is an authenticated page', () => {
describe('guided onboarding is enabled', () => {
const guidedOnboarding = guidedOnboardingMock.createStart();
test('registers the Onboarding Setup Guide link when cloud and guided onboarding is enabled and it is an authenticated page', () => {
const coreStart = coreMock.createStart();
coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
plugin.start(coreStart, { cloud, guidedOnboarding });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1);
});
test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => {
const coreStart = coreMock.createStart();
coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true);
const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
plugin.start(coreStart, { cloud, guidedOnboarding });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
});
test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => {
const coreStart = coreMock.createStart();
const cloud = { ...cloudMock.createStart(), isCloudEnabled: false };
plugin.start(coreStart, { cloud, guidedOnboarding });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
});
});
test('do not register the Onboarding Setup Guide link when guided onboarding is disabled', () => {
let { guidedOnboardingApi } = guidedOnboardingMock.createStart();
guidedOnboardingApi = {
...guidedOnboardingApi!,
isEnabled: false,
};
const guidedOnboarding = { guidedOnboardingApi };
const coreStart = coreMock.createStart();
coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
plugin.start(coreStart, { cloud });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1);
});
test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => {
const coreStart = coreMock.createStart();
coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true);
const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
plugin.start(coreStart, { cloud });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
});
test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => {
const coreStart = coreMock.createStart();
const cloud = { ...cloudMock.createStart(), isCloudEnabled: false };
plugin.start(coreStart, { cloud });
plugin.start(coreStart, { cloud, guidedOnboarding });
expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
});
});

View file

@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { CoreStart, Plugin } from '@kbn/core/public';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { maybeAddCloudLinks } from './maybe_add_cloud_links';
interface CloudLinksDepsSetup {
@ -20,6 +21,7 @@ interface CloudLinksDepsSetup {
interface CloudLinksDepsStart {
cloud?: CloudStart;
security?: SecurityPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
export class CloudLinksPlugin
@ -27,18 +29,19 @@ export class CloudLinksPlugin
{
public setup() {}
public start(core: CoreStart, { cloud, security }: CloudLinksDepsStart) {
public start(core: CoreStart, { cloud, security, guidedOnboarding }: CloudLinksDepsStart) {
if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) {
core.chrome.registerGlobalHelpExtensionMenuLink({
linkType: 'custom',
href: core.http.basePath.prepend('/app/home#/getting_started'),
content: (
<FormattedMessage id="xpack.cloudLinks.setupGuide" defaultMessage="Setup guides" />
),
'data-test-subj': 'cloudOnboardingSetupGuideLink',
priority: 1000, // We want this link to be at the very top.
});
if (guidedOnboarding?.guidedOnboardingApi?.isEnabled) {
core.chrome.registerGlobalHelpExtensionMenuLink({
linkType: 'custom',
href: core.http.basePath.prepend('/app/home#/getting_started'),
content: (
<FormattedMessage id="xpack.cloudLinks.setupGuide" defaultMessage="Setup guides" />
),
'data-test-subj': 'cloudOnboardingSetupGuideLink',
priority: 1000, // We want this link to be at the very top.
});
}
if (security) {
maybeAddCloudLinks({ security, chrome: core.chrome, cloud });
}

View file

@ -16,6 +16,7 @@
"@kbn/security-plugin",
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/guided-onboarding-plugin",
],
"exclude": [
"target/**/*",

View file

@ -105,6 +105,7 @@ export default function ({ getService }: FtrProviderContext) {
'advancedSettings',
'indexPatterns',
'graph',
'guidedOnboardingFeature',
'monitoring',
'observabilityCases',
'savedObjectsManagement',

View file

@ -104,6 +104,7 @@ export default function ({ getService }: FtrProviderContext) {
'readFlappingSettings',
],
maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'],
guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'],
},
reserved: ['fleet-setup', 'ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'],
};

View file

@ -50,6 +50,7 @@ export default function ({ getService }: FtrProviderContext) {
filesSharedImage: ['all', 'read', 'minimal_all', 'minimal_read'],
rulesSettings: ['all', 'read', 'minimal_all', 'minimal_read'],
maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'],
guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'],
},
global: ['all', 'read'],
space: ['all', 'read'],
@ -176,6 +177,7 @@ export default function ({ getService }: FtrProviderContext) {
'readFlappingSettings',
],
maintenanceWindow: ['all', 'read', 'minimal_all', 'minimal_read'],
guidedOnboardingFeature: ['all', 'read', 'minimal_all', 'minimal_read'],
},
reserved: ['fleet-setup', 'ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'],
};

View file

@ -45,6 +45,19 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.except('monitoring'));
break;
case 'everything_space_all at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(
navLinksBuilder.except(
'monitoring',
'enterpriseSearch',
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'appSearch',
'workplaceSearch'
)
);
break;
case 'global_read at everything_space':
case 'dual_privileges_read at everything_space':
case 'everything_space_read at everything_space':
@ -57,7 +70,8 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'appSearch',
'workplaceSearch'
'workplaceSearch',
'guidedOnboardingFeature'
)
);
break;