mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[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:
parent
207c23e2da
commit
b75546f7eb
23 changed files with 230 additions and 82 deletions
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"server": true,
|
||||
"browser": true,
|
||||
"optionalPlugins": [
|
||||
"cloud"
|
||||
"cloud",
|
||||
"features"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact"
|
||||
|
|
|
@ -28,6 +28,7 @@ const apiServiceMock: jest.Mocked<GuidedOnboardingPluginStart> = {
|
|||
isGuidePanelOpen$: new BehaviorSubject(false),
|
||||
isLoading$: new BehaviorSubject(false),
|
||||
getGuideConfig: jest.fn(),
|
||||
isEnabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -62,4 +62,5 @@ export interface GuidedOnboardingApi {
|
|||
isGuidePanelOpen$: Observable<boolean>;
|
||||
isLoading$: Observable<boolean>;
|
||||
getGuideConfig: (guideId: GuideId) => Promise<GuideConfig | undefined>;
|
||||
readonly isEnabled: boolean;
|
||||
}
|
||||
|
|
42
src/plugins/guided_onboarding/server/feature.ts
Normal file
42
src/plugins/guided_onboarding/server/feature.ts
Normal 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: [],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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]) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@kbn/core-http-browser",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/features-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"browser": true,
|
||||
"optionalPlugins": [
|
||||
"cloud",
|
||||
"security"
|
||||
"security",
|
||||
"guided_onboarding"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"@kbn/security-plugin",
|
||||
"@kbn/i18n",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/guided-onboarding-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -105,6 +105,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'advancedSettings',
|
||||
'indexPatterns',
|
||||
'graph',
|
||||
'guidedOnboardingFeature',
|
||||
'monitoring',
|
||||
'observabilityCases',
|
||||
'savedObjectsManagement',
|
||||
|
|
|
@ -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'],
|
||||
};
|
||||
|
|
|
@ -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'],
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue