mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Spaces] Add solution view select in UI (#186178)
This commit is contained in:
parent
6e905c24dd
commit
37ca5c6bd9
20 changed files with 506 additions and 19 deletions
|
@ -326,6 +326,7 @@ enabled:
|
|||
- x-pack/test/functional/apps/search_playground/config.ts
|
||||
- x-pack/test/functional/apps/snapshot_restore/config.ts
|
||||
- x-pack/test/functional/apps/spaces/config.ts
|
||||
- x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts
|
||||
- x-pack/test/functional/apps/status_page/config.ts
|
||||
- x-pack/test/functional/apps/transform/creation/index_pattern/config.ts
|
||||
- x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"home",
|
||||
"management",
|
||||
"usageCollection",
|
||||
"cloud"
|
||||
"cloud",
|
||||
"cloudExperiments"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
|
|
|
@ -22,3 +22,5 @@ export const getSpacesFeatureDescription = () => {
|
|||
export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', {
|
||||
defaultMessage: 'object',
|
||||
});
|
||||
|
||||
export const SOLUTION_NAV_FEATURE_FLAG_NAME = 'solutionNavEnabled';
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
exports[`renders correctly 1`] = `
|
||||
<SectionPanel
|
||||
dataTestSubj="generalPanel"
|
||||
title="General"
|
||||
>
|
||||
<EuiDescribedFormGroup
|
||||
|
|
|
@ -58,7 +58,7 @@ export class CustomizeSpace extends Component<Props, State> {
|
|||
});
|
||||
|
||||
return (
|
||||
<SectionPanel title={panelTitle}>
|
||||
<SectionPanel title={panelTitle} dataTestSubj="generalPanel">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="xs">
|
||||
|
|
|
@ -10,12 +10,13 @@ import { EuiButton } from '@elastic/eui';
|
|||
import { waitFor } from '@testing-library/react';
|
||||
import type { ReactWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
||||
import { notificationServiceMock, scopedHistoryMock } from '@kbn/core/public/mocks';
|
||||
import { KibanaFeature } from '@kbn/features-plugin/public';
|
||||
import { featuresPluginMock } from '@kbn/features-plugin/public/mocks';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal';
|
||||
import { EnabledFeatures } from './enabled_features';
|
||||
|
@ -105,6 +106,93 @@ describe('ManageSpacePage', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows solution view select when enabled', async () => {
|
||||
const spacesManager = spacesManagerMock.create();
|
||||
spacesManager.createSpace = jest.fn(spacesManager.createSpace);
|
||||
spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<ManageSpacePage
|
||||
spacesManager={spacesManager as unknown as SpacesManager}
|
||||
getFeatures={featuresStart.getFeatures}
|
||||
notifications={notificationServiceMock.createStartContract()}
|
||||
history={history}
|
||||
capabilities={{
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
spaces: { manage: true },
|
||||
}}
|
||||
allowFeatureVisibility
|
||||
isSolutionNavEnabled$={of(true)}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input[name="name"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('hides solution view select when not enabled or undefined', async () => {
|
||||
const spacesManager = spacesManagerMock.create();
|
||||
spacesManager.createSpace = jest.fn(spacesManager.createSpace);
|
||||
spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space);
|
||||
|
||||
{
|
||||
const wrapper = mountWithIntl(
|
||||
<ManageSpacePage
|
||||
spacesManager={spacesManager as unknown as SpacesManager}
|
||||
getFeatures={featuresStart.getFeatures}
|
||||
notifications={notificationServiceMock.createStartContract()}
|
||||
history={history}
|
||||
capabilities={{
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
spaces: { manage: true },
|
||||
}}
|
||||
allowFeatureVisibility
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input[name="name"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0);
|
||||
}
|
||||
|
||||
{
|
||||
const wrapper = mountWithIntl(
|
||||
<ManageSpacePage
|
||||
spacesManager={spacesManager as unknown as SpacesManager}
|
||||
getFeatures={featuresStart.getFeatures}
|
||||
notifications={notificationServiceMock.createStartContract()}
|
||||
history={history}
|
||||
capabilities={{
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
spaces: { manage: true },
|
||||
}}
|
||||
allowFeatureVisibility
|
||||
isSolutionNavEnabled$={of(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input[name="name"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('shows feature visibility controls when allowed', async () => {
|
||||
const spacesManager = spacesManagerMock.create();
|
||||
spacesManager.createSpace = jest.fn(spacesManager.createSpace);
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { difference } from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import type { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import type { Capabilities, NotificationsStart, ScopedHistory } from '@kbn/core/public';
|
||||
import { SectionLoading } from '@kbn/es-ui-shared-plugin/public';
|
||||
|
@ -29,6 +30,7 @@ import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal
|
|||
import { CustomizeSpace } from './customize_space';
|
||||
import { DeleteSpacesButton } from './delete_spaces_button';
|
||||
import { EnabledFeatures } from './enabled_features';
|
||||
import { SolutionView } from './solution_view';
|
||||
import type { Space } from '../../../common';
|
||||
import { isReservedSpace } from '../../../common';
|
||||
import { getSpacesFeatureDescription } from '../../constants';
|
||||
|
@ -54,6 +56,7 @@ interface Props {
|
|||
capabilities: Capabilities;
|
||||
history: ScopedHistory;
|
||||
allowFeatureVisibility: boolean;
|
||||
isSolutionNavEnabled$?: Observable<boolean>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -67,10 +70,13 @@ interface State {
|
|||
isInvalid: boolean;
|
||||
error?: string;
|
||||
};
|
||||
isSolutionNavEnabled: boolean;
|
||||
}
|
||||
|
||||
export class ManageSpacePage extends Component<Props, State> {
|
||||
private readonly validator: SpaceValidator;
|
||||
private initialSpaceState: State['space'] | null = null;
|
||||
private subscription: Subscription | null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
@ -83,6 +89,7 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
color: getSpaceColor({}),
|
||||
},
|
||||
features: [],
|
||||
isSolutionNavEnabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -107,6 +114,12 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.isSolutionNavEnabled$) {
|
||||
this.subscription = this.props.isSolutionNavEnabled$.subscribe((isEnabled) => {
|
||||
this.setState({ isSolutionNavEnabled: isEnabled });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidUpdate(previousProps: Props) {
|
||||
|
@ -115,6 +128,12 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (!this.props.capabilities.spaces.manage) {
|
||||
return (
|
||||
|
@ -161,6 +180,13 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
validator={this.validator}
|
||||
/>
|
||||
|
||||
{this.state.isSolutionNavEnabled && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<SolutionView space={this.state.space} onChange={this.onSpaceChange} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.props.allowFeatureVisibility && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
@ -298,8 +324,10 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
const haveDisabledFeaturesChanged =
|
||||
space.disabledFeatures.length !== originalSpace.disabledFeatures.length ||
|
||||
difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0;
|
||||
const hasSolutionViewChanged =
|
||||
this.state.space.solution !== this.initialSpaceState?.solution;
|
||||
|
||||
if (editingActiveSpace && haveDisabledFeaturesChanged) {
|
||||
if (editingActiveSpace && (haveDisabledFeaturesChanged || hasSolutionViewChanged)) {
|
||||
this.setState({
|
||||
showAlteringActiveSpaceDialog: true,
|
||||
});
|
||||
|
@ -326,17 +354,19 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
onLoadSpace(space);
|
||||
}
|
||||
|
||||
this.initialSpaceState = {
|
||||
...space,
|
||||
avatarType: space.imageUrl ? 'image' : 'initials',
|
||||
initials: space.initials || getSpaceInitials(space),
|
||||
color: space.color || getSpaceColor(space),
|
||||
customIdentifier: false,
|
||||
customAvatarInitials:
|
||||
!!space.initials && getSpaceInitials({ name: space.name }) !== space.initials,
|
||||
customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color,
|
||||
};
|
||||
|
||||
this.setState({
|
||||
space: {
|
||||
...space,
|
||||
avatarType: space.imageUrl ? 'image' : 'initials',
|
||||
initials: space.initials || getSpaceInitials(space),
|
||||
color: space.color || getSpaceColor(space),
|
||||
customIdentifier: false,
|
||||
customAvatarInitials:
|
||||
!!space.initials && getSpaceInitials({ name: space.name }) !== space.initials,
|
||||
customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color,
|
||||
},
|
||||
space: { ...this.initialSpaceState },
|
||||
features,
|
||||
originalSpace: space,
|
||||
isLoading: false,
|
||||
|
@ -369,6 +399,7 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
disabledFeatures = [],
|
||||
imageUrl,
|
||||
avatarType,
|
||||
solution,
|
||||
} = this.state.space;
|
||||
|
||||
const params = {
|
||||
|
@ -379,6 +410,7 @@ export class ManageSpacePage extends Component<Props, State> {
|
|||
color: color ? hsvToHex(hexToHsv(color)).toUpperCase() : color, // Convert 3 digit hex codes to 6 digits since Spaces API requires 6 digits
|
||||
disabledFeatures,
|
||||
imageUrl: avatarType === 'image' ? imageUrl : '',
|
||||
solution,
|
||||
};
|
||||
|
||||
let action;
|
||||
|
|
|
@ -13,12 +13,13 @@ import React, { Component, Fragment } from 'react';
|
|||
interface Props {
|
||||
iconType?: IconType;
|
||||
title: string | ReactNode;
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
||||
export class SectionPanel extends Component<Props, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder={true}>
|
||||
<EuiPanel hasShadow={false} hasBorder={true} data-test-subj={this.props.dataTestSubj}>
|
||||
{this.getTitle()}
|
||||
{this.getForm()}
|
||||
</EuiPanel>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { SolutionView } from './solution_view';
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiSuperSelectOption, EuiThemeComputed } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiSuperSelect,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import type { FunctionComponent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { Space } from '../../../../common';
|
||||
import { SectionPanel } from '../section_panel';
|
||||
|
||||
type SolutionView = Space['solution'];
|
||||
|
||||
const getOptions = ({ size }: EuiThemeComputed): Array<EuiSuperSelectOption<SolutionView>> => {
|
||||
const iconCss = { marginRight: size.m };
|
||||
|
||||
return [
|
||||
{
|
||||
value: 'es',
|
||||
inputDisplay: (
|
||||
<>
|
||||
<EuiIcon type="logoElasticsearch" css={iconCss} />
|
||||
{i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.solutionViewSelect.searchOptionLabel',
|
||||
{
|
||||
defaultMessage: 'Search',
|
||||
}
|
||||
)}
|
||||
</>
|
||||
),
|
||||
'data-test-subj': 'solutionViewEsOption',
|
||||
},
|
||||
{
|
||||
value: 'oblt',
|
||||
inputDisplay: (
|
||||
<>
|
||||
<EuiIcon type="logoObservability" css={iconCss} />
|
||||
{i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.solutionViewSelect.obltOptionLabel',
|
||||
{
|
||||
defaultMessage: 'Observability',
|
||||
}
|
||||
)}
|
||||
</>
|
||||
),
|
||||
'data-test-subj': 'solutionViewObltOption',
|
||||
},
|
||||
{
|
||||
value: 'security',
|
||||
inputDisplay: (
|
||||
<>
|
||||
<EuiIcon type="logoSecurity" css={iconCss} />
|
||||
{i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.solutionViewSelect.securityOptionLabel',
|
||||
{
|
||||
defaultMessage: 'Security',
|
||||
}
|
||||
)}
|
||||
</>
|
||||
),
|
||||
'data-test-subj': 'solutionViewSecurityOption',
|
||||
},
|
||||
{
|
||||
value: 'classic',
|
||||
inputDisplay: (
|
||||
<>
|
||||
<EuiIcon type="logoKibana" css={iconCss} />
|
||||
{i18n.translate(
|
||||
'xpack.spaces.management.manageSpacePage.solutionViewSelect.classicOptionLabel',
|
||||
{
|
||||
defaultMessage: 'Classic',
|
||||
}
|
||||
)}
|
||||
</>
|
||||
),
|
||||
'data-test-subj': 'solutionViewClassicOption',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
interface Props {
|
||||
space: Partial<Space>;
|
||||
onChange: (space: Partial<Space>) => void;
|
||||
}
|
||||
|
||||
export const SolutionView: FunctionComponent<Props> = ({ space, onChange }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<SectionPanel
|
||||
title={i18n.translate('xpack.spaces.management.manageSpacePage.navigationTitle', {
|
||||
defaultMessage: 'Navigation',
|
||||
})}
|
||||
dataTestSubj="navigationPanel"
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.manageSpacePage.setSolutionViewMessage"
|
||||
defaultMessage="Set solution view"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.manageSpacePage.setSolutionViewDescription"
|
||||
defaultMessage="Determines the navigation all users will see for this space. Each solution view contains features from Analytics tools and Management."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.spaces.management.navigation.solutionViewLabel', {
|
||||
defaultMessage: 'Solution view',
|
||||
})}
|
||||
fullWidth
|
||||
>
|
||||
<EuiSuperSelect
|
||||
options={getOptions(euiTheme)}
|
||||
valueOfSelected={space.solution}
|
||||
data-test-subj="solutionViewSelect"
|
||||
onChange={(solution) => {
|
||||
onChange({ ...space, solution });
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</SectionPanel>
|
||||
);
|
||||
};
|
|
@ -125,7 +125,7 @@ describe('spacesManagementApp', () => {
|
|||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
data-test-subj="kbnRedirectAppLink"
|
||||
>
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true}
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true,"isSolutionNavEnabled$":{}}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
@ -158,7 +158,7 @@ describe('spacesManagementApp', () => {
|
|||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
data-test-subj="kbnRedirectAppLink"
|
||||
>
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true}
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"isSolutionNavEnabled$":{}}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { from, of, shareReplay } from 'rxjs';
|
||||
|
||||
import type { StartServicesAccessor } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -20,6 +21,7 @@ import { Route, Router, Routes } from '@kbn/shared-ux-router';
|
|||
|
||||
import type { Space } from '../../common';
|
||||
import type { ConfigType } from '../config';
|
||||
import { SOLUTION_NAV_FEATURE_FLAG_NAME } from '../constants';
|
||||
import type { PluginsStart } from '../plugin';
|
||||
import type { SpacesManager } from '../spaces_manager';
|
||||
|
||||
|
@ -43,8 +45,15 @@ export const spacesManagementApp = Object.freeze({
|
|||
title,
|
||||
|
||||
async mount({ element, setBreadcrumbs, history }) {
|
||||
const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] =
|
||||
await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]);
|
||||
const [
|
||||
[coreStart, { features, cloud, cloudExperiments }],
|
||||
{ SpacesGridPage },
|
||||
{ ManageSpacePage },
|
||||
] = await Promise.all([
|
||||
getStartServices(),
|
||||
import('./spaces_grid'),
|
||||
import('./edit_space'),
|
||||
]);
|
||||
|
||||
const spacesFirstBreadcrumb = {
|
||||
text: title,
|
||||
|
@ -54,6 +63,17 @@ export const spacesManagementApp = Object.freeze({
|
|||
|
||||
chrome.docTitle.change(title);
|
||||
|
||||
const onCloud = Boolean(cloud?.isCloudEnabled);
|
||||
const isSolutionNavEnabled$ =
|
||||
// Only available on Cloud and if the Launch Darkly flag is turned on
|
||||
onCloud && cloudExperiments
|
||||
? from(
|
||||
cloudExperiments
|
||||
.getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false)
|
||||
.catch(() => false)
|
||||
).pipe(shareReplay(1))
|
||||
: of(false);
|
||||
|
||||
const SpacesGridPageWithBreadcrumbs = () => {
|
||||
setBreadcrumbs([{ ...spacesFirstBreadcrumb, href: undefined }]);
|
||||
return (
|
||||
|
@ -87,6 +107,7 @@ export const spacesManagementApp = Object.freeze({
|
|||
spacesManager={spacesManager}
|
||||
history={history}
|
||||
allowFeatureVisibility={config.allowFeatureVisibility}
|
||||
isSolutionNavEnabled$={isSolutionNavEnabled$}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -113,6 +134,7 @@ export const spacesManagementApp = Object.freeze({
|
|||
onLoadSpace={onLoadSpace}
|
||||
history={history}
|
||||
allowFeatureVisibility={config.allowFeatureVisibility}
|
||||
isSolutionNavEnabled$={isSolutionNavEnabled$}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common';
|
||||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
|
||||
import type { FeaturesPluginStart } from '@kbn/features-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
|
@ -23,11 +25,14 @@ import { getUiApi } from './ui_api';
|
|||
export interface PluginsSetup {
|
||||
home?: HomePublicPluginSetup;
|
||||
management?: ManagementSetup;
|
||||
cloud?: CloudSetup;
|
||||
}
|
||||
|
||||
export interface PluginsStart {
|
||||
features: FeaturesPluginStart;
|
||||
management?: ManagementStart;
|
||||
cloud?: CloudStart;
|
||||
cloudExperiments?: CloudExperimentsPluginStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"@kbn/utility-types-jest",
|
||||
"@kbn/security-plugin-types-public",
|
||||
"@kbn/cloud-plugin",
|
||||
"@kbn/cloud-experiments-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
36
x-pack/test/functional/apps/spaces/create_edit_space.ts
Normal file
36
x-pack/test/functional/apps/spaces/create_edit_space.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('edit space', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
describe('solution view', () => {
|
||||
it('does not show solution view panel', async () => {
|
||||
await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
await testSubjects.existOrFail('spaces-edit-page');
|
||||
await testSubjects.existOrFail('spaces-edit-page > generalPanel');
|
||||
await testSubjects.missingOrFail('spaces-edit-page > navigationPanel');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -13,5 +13,6 @@ export default function spacesApp({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./feature_controls/spaces_security'));
|
||||
loadTestFile(require.resolve('./spaces_selection'));
|
||||
loadTestFile(require.resolve('./enter_space'));
|
||||
loadTestFile(require.resolve('./create_edit_space'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrConfigProviderContext } from '@kbn/test';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js'));
|
||||
|
||||
return {
|
||||
...functionalConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
'--xpack.cloud_integrations.experiments.enabled=true',
|
||||
'--xpack.cloud_integrations.experiments.launch_darkly.sdk_key=a_string',
|
||||
'--xpack.cloud_integrations.experiments.launch_darkly.client_id=a_string',
|
||||
'--xpack.cloud_integrations.experiments.flag_overrides.solutionNavEnabled=true',
|
||||
// Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests
|
||||
'--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=',
|
||||
'--xpack.cloud.base_url=https://cloud.elastic.co',
|
||||
'--xpack.cloud.deployment_url=/deployments/deploymentId',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const find = getService('find');
|
||||
|
||||
describe('edit space', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
describe('solution view', () => {
|
||||
it('does show the solution view panel', async () => {
|
||||
await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
await testSubjects.existOrFail('spaces-edit-page');
|
||||
await testSubjects.existOrFail('spaces-edit-page > generalPanel');
|
||||
await testSubjects.existOrFail('spaces-edit-page > navigationPanel');
|
||||
});
|
||||
|
||||
it('changes the space solution and updates the side navigation', async () => {
|
||||
await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
// Make sure we are on the classic side nav
|
||||
await testSubjects.existOrFail('mgtSideBarNav');
|
||||
await testSubjects.missingOrFail('searchSideNav');
|
||||
|
||||
// change to Enterprise Search
|
||||
await PageObjects.spaceSelector.changeSolutionView('es');
|
||||
await PageObjects.spaceSelector.clickSaveSpaceCreation();
|
||||
await PageObjects.spaceSelector.confirmModal();
|
||||
|
||||
await find.waitForDeletedByCssSelector('.kibanaWelcomeLogo');
|
||||
|
||||
// Search side nav is loaded
|
||||
await testSubjects.existOrFail('searchSideNav');
|
||||
await testSubjects.missingOrFail('mgtSideBarNav');
|
||||
|
||||
// change back to classic
|
||||
await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
await PageObjects.spaceSelector.changeSolutionView('classic');
|
||||
await PageObjects.spaceSelector.clickSaveSpaceCreation();
|
||||
await PageObjects.spaceSelector.confirmModal();
|
||||
|
||||
await testSubjects.existOrFail('mgtSideBarNav');
|
||||
await testSubjects.missingOrFail('searchSideNav');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function spacesApp({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Spaces app (with solution view)', function spacesAppTestSuite() {
|
||||
loadTestFile(require.resolve('./create_edit_space'));
|
||||
});
|
||||
}
|
|
@ -120,6 +120,22 @@ export class SpaceSelectorPageObject extends FtrService {
|
|||
await this.testSubjects.setValue('euiColorPickerAnchor', hexValue);
|
||||
}
|
||||
|
||||
async openSolutionViewSelect() {
|
||||
const solutionViewSelect = await this.testSubjects.find('solutionViewSelect');
|
||||
const classes = await solutionViewSelect.getAttribute('class');
|
||||
|
||||
const isOpen = classes?.includes('isOpen') ?? false;
|
||||
if (!isOpen) {
|
||||
await solutionViewSelect.click();
|
||||
}
|
||||
}
|
||||
|
||||
async changeSolutionView(solution: 'es' | 'oblt' | 'security' | 'classic') {
|
||||
await this.openSolutionViewSelect();
|
||||
const serialized = solution.charAt(0).toUpperCase() + solution.slice(1);
|
||||
await this.testSubjects.click(`solutionView${serialized}Option`);
|
||||
}
|
||||
|
||||
async clickShowFeatures() {
|
||||
await this.testSubjects.click('show-hide-section-link');
|
||||
}
|
||||
|
@ -208,6 +224,11 @@ export class SpaceSelectorPageObject extends FtrService {
|
|||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
}
|
||||
|
||||
// Generic for any confirm modal
|
||||
async confirmModal() {
|
||||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
}
|
||||
|
||||
async clickOnSpaceb() {
|
||||
await this.testSubjects.click('space-avatar-space_b');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue