diff --git a/docs/spaces/images/edit-space-feature-visibility.png b/docs/spaces/images/edit-space-feature-visibility.png
index 0132337f4a7c6..f1852d3cc03b5 100644
Binary files a/docs/spaces/images/edit-space-feature-visibility.png and b/docs/spaces/images/edit-space-feature-visibility.png differ
diff --git a/docs/spaces/images/edit-space.png b/docs/spaces/images/edit-space.png
index c78e0d14b0d4c..9785dd9e77aba 100644
Binary files a/docs/spaces/images/edit-space.png and b/docs/spaces/images/edit-space.png differ
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap
new file mode 100644
index 0000000000000..51943c7581273
--- /dev/null
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap
@@ -0,0 +1,164 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+ }
+ labelType="label"
+ >
+
+
+
+ }
+ isInvalid={true}
+ label={
+
+ }
+ labelType="label"
+ >
+
+
+
+
+
+ Choose how your space avatar appears across Kibana.
+
+ }
+ >
+
+
+
+ }
+ fullWidth={true}
+ title={
+
+
+
+
+
+ }
+ >
+
+
+
+`;
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap
index 058b9ecdd0f8f..3d709a5dca45a 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap
@@ -7,58 +7,67 @@ exports[`renders without crashing 1`] = `
-
-
-
-
-
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap
deleted file mode 100644
index f1b4b956eca88..0000000000000
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap
+++ /dev/null
@@ -1,63 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders without crashing 1`] = `
-
-
-
- /s/
-
- ,
- }
- }
- />
-
- }
- isInvalid={false}
- label={
-
-
-
-
-
-
- }
- labelType="label"
- >
-
-
-
-`;
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx
new file mode 100644
index 0000000000000..42195317e6731
--- /dev/null
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 React from 'react';
+
+import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest';
+
+import { SpaceValidator } from '../../lib';
+import { CustomizeSpace } from './customize_space';
+
+const validator = new SpaceValidator({ shouldValidate: true });
+
+test('renders correctly', () => {
+ const wrapper = shallowWithIntl(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('updates identifier, initials and color when name is changed', () => {
+ const space = {
+ id: 'space-1',
+ name: 'Space 1',
+ initials: 'S1',
+ color: '#ABCDEF',
+ };
+ const changeHandler = jest.fn();
+
+ const wrapper = mountWithIntl(
+
+ );
+
+ wrapper.find('input[name="name"]').simulate('change', { target: { value: 'Space 2' } });
+
+ expect(changeHandler).toHaveBeenCalledWith({
+ ...space,
+ id: 'space-2',
+ name: 'Space 2',
+ initials: 'S2',
+ color: '#9170B8',
+ });
+});
+
+test('does not update custom identifier, initials or color name is changed', () => {
+ const space = {
+ id: 'space-1',
+ name: 'Space 1',
+ initials: 'S1',
+ color: '#ABCDEF',
+ customAvatarInitials: true,
+ customAvatarColor: true,
+ };
+ const changeHandler = jest.fn();
+
+ const wrapper = mountWithIntl(
+
+ );
+
+ wrapper.find('input[name="name"]').simulate('change', { target: { value: 'Space 2' } });
+
+ expect(changeHandler).toHaveBeenCalledWith({
+ ...space,
+ name: 'Space 2',
+ });
+});
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
index 4bbad58b5d139..27de0e012faf9 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx
@@ -5,31 +5,27 @@
* 2.0.
*/
-import type { EuiPopoverProps } from '@elastic/eui';
import {
EuiDescribedFormGroup,
EuiFieldText,
EuiFormRow,
EuiLoadingSpinner,
- EuiPopover,
- EuiSpacer,
+ EuiText,
EuiTextArea,
EuiTitle,
} from '@elastic/eui';
import type { ChangeEvent } from 'react';
-import React, { Component, Fragment, lazy, Suspense } from 'react';
+import React, { Component, lazy, Suspense } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import type { Space } from 'src/plugins/spaces_oss/common';
-import { isReservedSpace } from '../../../../common';
-import { getSpaceAvatarComponent } from '../../../space_avatar';
+import { getSpaceAvatarComponent, getSpaceColor, getSpaceInitials } from '../../../space_avatar';
import type { SpaceValidator } from '../../lib';
import { toSpaceIdentifier } from '../../lib';
+import type { FormValues } from '../manage_space_page';
import { SectionPanel } from '../section_panel';
import { CustomizeSpaceAvatar } from './customize_space_avatar';
-import { SpaceIdentifier } from './space_identifier';
// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana.
const LazySpaceAvatar = lazy(() =>
@@ -38,9 +34,9 @@ const LazySpaceAvatar = lazy(() =>
interface Props {
validator: SpaceValidator;
- space: Partial;
+ space: FormValues;
editingExistingSpace: boolean;
- onChange: (space: Partial) => void;
+ onChange: (space: FormValues) => void;
}
interface State {
@@ -55,33 +51,31 @@ export class CustomizeSpace extends Component {
};
public render() {
- const { validator, editingExistingSpace } = this.props;
- const { name = '', description = '' } = this.props.space;
- const panelTitle = i18n.translate(
- 'xpack.spaces.management.manageSpacePage.customizeSpaceTitle',
- {
- defaultMessage: 'Customize your space',
- }
- );
-
- const extraPopoverProps: Partial = {
- initialFocus: 'input[name="spaceInitials"]',
- };
+ const { validator, editingExistingSpace, space } = this.props;
+ const { name = '', description = '' } = space;
+ const panelTitle = i18n.translate('xpack.spaces.management.manageSpacePage.generalTitle', {
+ defaultMessage: 'General',
+ });
return (
-
+
}
- description={this.getPanelDescription()}
+ description={i18n.translate(
+ 'xpack.spaces.management.manageSpacePage.describeSpaceDescription',
+ {
+ defaultMessage: "Give your space a name that's memorable.",
+ }
+ )}
fullWidth
>
{
-
-
- {this.props.space && isReservedSpace(this.props.space) ? null : (
-
-
-
- )}
-
+
+
+ }
helpText={i18n.translate(
'xpack.spaces.management.manageSpacePage.spaceDescriptionHelpText',
{
- defaultMessage: 'The description appears on the Space selection screen.',
+ defaultMessage: 'The description appears on the space selection screen.',
}
)}
{...validator.validateSpaceDescription(this.props.space)}
@@ -139,80 +123,97 @@ export class CustomizeSpace extends Component {
-
-
- }>
-
-
-
+ {editingExistingSpace ? null : (
+
+ }
+ helpText={
+
}
- closePopover={this.closePopover}
- {...extraPopoverProps}
- ownFocus={true}
- isOpen={this.state.customizingAvatar}
+ {...this.props.validator.validateURLIdentifier(this.props.space)}
+ fullWidth
>
-
-
-
-
-
+
+
+ )}
+
+
+
+
+
+
+
+ }
+ description={
+ <>
+
+ {i18n.translate('xpack.spaces.management.manageSpacePage.avatarDescription', {
+ defaultMessage: 'Choose how your space avatar appears across Kibana.',
+ })}
+
+ {space.avatarType === 'image' ? (
+ }>
+
+
+ ) : (
+ }>
+
+
+ )}
+ >
+ }
+ fullWidth
+ >
+
);
}
- public togglePopover = () => {
- this.setState({
- customizingAvatar: !this.state.customizingAvatar,
- });
- };
-
- public closePopover = () => {
- this.setState({
- customizingAvatar: false,
- });
- };
-
- public getPanelDescription = () => {
- return this.props.editingExistingSpace ? (
-
-
-
- ) : (
-
-
-
- );
- };
-
public onNameChange = (e: ChangeEvent) => {
if (!this.props.space) {
return;
@@ -230,6 +231,12 @@ export class CustomizeSpace extends Component {
...this.props.space,
name: e.target.value,
id,
+ initials: this.props.space.customAvatarInitials
+ ? this.props.space.initials
+ : getSpaceInitials({ name: e.target.value }),
+ color: this.props.space.customAvatarColor
+ ? this.props.space.color
+ : getSpaceColor({ name: e.target.value }),
});
};
@@ -240,7 +247,8 @@ export class CustomizeSpace extends Component {
});
};
- public onSpaceIdentifierChange = (updatedIdentifier: string) => {
+ public onSpaceIdentifierChange = (e: ChangeEvent) => {
+ const updatedIdentifier = e.target.value;
const usingCustomIdentifier = updatedIdentifier !== toSpaceIdentifier(this.props.space.name);
this.setState({
@@ -252,7 +260,7 @@ export class CustomizeSpace extends Component {
});
};
- public onAvatarChange = (space: Partial) => {
+ public onAvatarChange = (space: FormValues) => {
this.props.onChange(space);
};
}
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx
index babd89f69c784..578da9b96611c 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx
@@ -10,6 +10,7 @@ import React from 'react';
import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest';
+import { SpaceValidator } from '../../lib';
import { CustomizeSpaceAvatar } from './customize_space_avatar';
const space = {
@@ -17,13 +18,19 @@ const space = {
name: '',
};
+const validator = new SpaceValidator({ shouldValidate: true });
+
test('renders without crashing', () => {
- const wrapper = shallowWithIntl();
+ const wrapper = shallowWithIntl(
+
+ );
expect(wrapper).toMatchSnapshot();
});
test('shows customization fields', () => {
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
expect(wrapper.find(EuiLink)).toHaveLength(0);
expect(wrapper.find(EuiFieldText)).toHaveLength(2); // EuiColorPicker contains an EuiFieldText element
@@ -41,17 +48,14 @@ test('invokes onChange callback when avatar is customized', () => {
const changeHandler = jest.fn();
const wrapper = mountWithIntl(
-
+
);
- wrapper
- .find(EuiFieldText)
- .first()
- .find('input')
- .simulate('change', { target: { value: 'NV' } });
+ wrapper.find('input[name="spaceInitials"]').simulate('change', { target: { value: 'NV' } });
expect(changeHandler).toHaveBeenCalledWith({
...customizedSpace,
initials: 'NV',
+ customAvatarInitials: true,
});
});
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx
index 96cd094c14645..827ef592459f7 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx
@@ -6,46 +6,30 @@
*/
import {
- EuiButton,
+ EuiButtonGroup,
EuiColorPicker,
EuiFieldText,
EuiFilePicker,
- EuiFlexItem,
EuiFormRow,
- EuiSpacer,
- isValidHex,
} from '@elastic/eui';
import type { ChangeEvent } from 'react';
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
-import type { Space } from 'src/plugins/spaces_oss/common';
import { MAX_SPACE_INITIALS } from '../../../../common';
import { encode, imageTypes } from '../../../../common/lib/dataurl';
-import { getSpaceColor, getSpaceInitials } from '../../../space_avatar';
+import type { SpaceValidator } from '../../lib';
+import type { FormValues } from '../manage_space_page';
interface Props {
- space: Partial;
- onChange: (space: Partial) => void;
+ space: FormValues;
+ onChange: (space: FormValues) => void;
+ validator: SpaceValidator;
}
-interface State {
- initialsHasFocus: boolean;
- pendingInitials?: string | null;
-}
-
-export class CustomizeSpaceAvatar extends Component {
- private initialsRef: HTMLInputElement | null = null;
-
- constructor(props: Props) {
- super(props);
- this.state = {
- initialsHasFocus: false,
- };
- }
-
- private storeImageChanges(imageUrl: string) {
+export class CustomizeSpaceAvatar extends Component {
+ private storeImageChanges(imageUrl: string | undefined) {
this.props.onChange({
...this.props.space,
imageUrl,
@@ -96,7 +80,10 @@ export class CustomizeSpaceAvatar extends Component {
};
private onFileUpload = (files: FileList | null) => {
- if (files == null) return;
+ if (files == null || files.length === 0) {
+ this.storeImageChanges(undefined);
+ return;
+ }
const file = files[0];
if (imageTypes.indexOf(file.type) > -1) {
encode(file).then((dataurl: string) => this.handleImageUpload(dataurl));
@@ -106,130 +93,117 @@ export class CustomizeSpaceAvatar extends Component {
public render() {
const { space } = this.props;
- const { initialsHasFocus, pendingInitials } = this.state;
-
- const spaceColor = getSpaceColor(space);
- const isInvalidSpaceColor = !isValidHex(spaceColor) && spaceColor !== '';
-
return (
);
}
- private removeImageUrl() {
- this.props.onChange({
- ...this.props.space,
- imageUrl: '',
- });
- }
-
- public filePickerOrImage() {
- if (!this.props.space.imageUrl) {
- return (
-
-
-
- );
- } else {
- return (
-
- this.removeImageUrl()} color="danger" iconType="trash">
- {i18n.translate('xpack.spaces.management.customizeSpaceAvatar.removeImage', {
- defaultMessage: 'Remove custom image',
- })}
-
-
- );
- }
- }
-
- public initialsInputRef = (ref: HTMLInputElement) => {
- if (ref) {
- this.initialsRef = ref;
- this.initialsRef.addEventListener('focus', this.onInitialsFocus);
- this.initialsRef.addEventListener('blur', this.onInitialsBlur);
- } else {
- if (this.initialsRef) {
- this.initialsRef.removeEventListener('focus', this.onInitialsFocus);
- this.initialsRef.removeEventListener('blur', this.onInitialsBlur);
- this.initialsRef = null;
- }
- }
- };
-
- public onInitialsFocus = () => {
- this.setState({
- initialsHasFocus: true,
- pendingInitials: getSpaceInitials(this.props.space),
- });
- };
-
- public onInitialsBlur = () => {
- this.setState({
- initialsHasFocus: false,
- pendingInitials: null,
- });
- };
-
public onInitialsChange = (e: ChangeEvent) => {
const initials = (e.target.value || '').substring(0, MAX_SPACE_INITIALS);
- this.setState({
- pendingInitials: initials,
- });
-
this.props.onChange({
...this.props.space,
+ customAvatarInitials: true,
initials,
});
};
@@ -237,6 +211,7 @@ export class CustomizeSpaceAvatar extends Component {
public onColorChange = (color: string) => {
this.props.onChange({
...this.props.space,
+ customAvatarColor: true,
color,
});
};
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx
deleted file mode 100644
index f28e17d0e1f03..0000000000000
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 React from 'react';
-
-import { shallowWithIntl } from '@kbn/test/jest';
-
-import { SpaceValidator } from '../../lib';
-import { SpaceIdentifier } from './space_identifier';
-
-test('renders without crashing', () => {
- const props = {
- space: {
- id: '',
- name: '',
- },
- editable: true,
- onChange: jest.fn(),
- validator: new SpaceValidator(),
- };
- const wrapper = shallowWithIntl(
-
- );
- expect(wrapper).toMatchSnapshot();
-});
diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx
deleted file mode 100644
index bc863e4add2cc..0000000000000
--- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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 { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui';
-import type { ChangeEvent } from 'react';
-import React, { Component, Fragment } from 'react';
-
-import type { InjectedIntl } from '@kbn/i18n/react';
-import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
-import type { Space } from 'src/plugins/spaces_oss/common';
-
-import type { SpaceValidator } from '../../lib';
-import { toSpaceIdentifier } from '../../lib';
-
-interface Props {
- space: Partial;
- editable: boolean;
- validator: SpaceValidator;
- intl: InjectedIntl;
- onChange: (updatedIdentifier: string) => void;
-}
-
-interface State {
- editing: boolean;
-}
-
-class SpaceIdentifierUI extends Component {
- private textFieldRef: HTMLInputElement | null = null;
-
- constructor(props: Props) {
- super(props);
- this.state = {
- editing: false,
- };
- }
-
- public render() {
- const { intl } = this.props;
- const { id = '' } = this.props.space;
-
- return (
-
-
- (this.textFieldRef = ref)}
- fullWidth
- />
-
-
- );
- }
-
- public getLabel = () => {
- if (!this.props.editable) {
- return (
-
-
-
- );
- }
-
- const editLinkText = this.state.editing ? (
-
- ) : (
-
- );
-
- const editLinkLabel = this.state.editing
- ? this.props.intl.formatMessage({
- id: 'xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkLabel',
- defaultMessage: 'Reset the URL identifier',
- })
- : this.props.intl.formatMessage({
- id: 'xpack.spaces.management.spaceIdentifier.customizeSpaceNameLinkLabel',
- defaultMessage: 'Customize the URL identifier',
- });
-
- return (
-
-
-
- {editLinkText}
-
-
- );
- };
-
- public getHelpText = (
- identifier: string = this.props.intl.formatMessage({
- id: 'xpack.spaces.management.spaceIdentifier.emptySpaceIdentifierText',
- defaultMessage: 'awesome-space',
- })
- ) => {
- return (
-
- /s/{identifier},
- }}
- />
-
- );
- };
-
- public onEditClick = () => {
- const currentlyEditing = this.state.editing;
- if (currentlyEditing) {
- // "Reset" clicked. Create space identifier based on the space name.
- const resetIdentifier = toSpaceIdentifier(this.props.space.name);
-
- this.setState({
- editing: false,
- });
- this.props.onChange(resetIdentifier);
- } else {
- this.setState(
- {
- editing: true,
- },
- () => {
- if (this.textFieldRef) {
- this.textFieldRef.focus();
- }
- }
- );
- }
- };
-
- public onChange = (e: ChangeEvent) => {
- if (!this.state.editing) {
- return;
- }
- this.props.onChange(e.target.value);
- };
-}
-
-export const SpaceIdentifier = injectI18n(SpaceIdentifierUI);
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
index c22a25ef60c31..b23b504bd0264 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap
@@ -3,34 +3,7 @@
exports[`EnabledFeatures renders as expected 1`] = `
-
-
-
-
-
-
-
-
- }
+ title="Features"
>
@@ -39,7 +12,7 @@ exports[`EnabledFeatures renders as expected 1`] = `
>
@@ -54,9 +27,17 @@ exports[`EnabledFeatures renders as expected 1`] = `
>
,
+ }
+ }
/>
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx
index 7f1ea57a6b89c..af6c546fcf56a 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx
@@ -32,11 +32,9 @@ const features: KibanaFeatureConfig[] = [
];
describe('EnabledFeatures', () => {
- const getUrlForApp = (appId: string) => appId;
-
it(`renders as expected`, () => {
expect(
- shallowWithIntl(
+ shallowWithIntl(
{
disabledFeatures: ['feature-1', 'feature-2'],
}}
onChange={jest.fn()}
- getUrlForApp={getUrlForApp}
/>
)
).toMatchSnapshot();
@@ -63,7 +60,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: ['feature-1', 'feature-2'],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
@@ -97,7 +93,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: [],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
@@ -134,7 +129,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: [],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
@@ -164,7 +158,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: ['feature-1', 'feature-2'],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
@@ -192,7 +185,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: ['feature-1'],
}}
onChange={jest.fn()}
- getUrlForApp={getUrlForApp}
/>
);
expect(findTestSubject(wrapper, 'hideAllFeaturesLink')).toHaveLength(1);
@@ -211,7 +203,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: [],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
@@ -239,7 +230,6 @@ describe('EnabledFeatures', () => {
disabledFeatures: [],
}}
onChange={changeHandler}
- getUrlForApp={getUrlForApp}
/>
);
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx
index 667878df3254a..7481676430307 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx
@@ -5,17 +5,16 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
-import type { ReactNode } from 'react';
-import React, { Component, Fragment } from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } 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 { ApplicationStart } from 'src/core/public';
import type { Space } from 'src/plugins/spaces_oss/common';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import type { KibanaFeatureConfig } from '../../../../../features/public';
-import { getEnabledFeatures } from '../../lib/feature_utils';
import { SectionPanel } from '../section_panel';
import { FeatureTable } from './feature_table';
@@ -23,117 +22,62 @@ interface Props {
space: Partial;
features: KibanaFeatureConfig[];
onChange: (space: Partial) => void;
- getUrlForApp: ApplicationStart['getUrlForApp'];
}
-export class EnabledFeatures extends Component {
- public render() {
- const description = i18n.translate(
- 'xpack.spaces.management.manageSpacePage.customizeVisibleFeatures',
- {
- defaultMessage: 'Customize visible features',
- }
- );
+export const EnabledFeatures: FunctionComponent = (props) => {
+ const { services } = useKibana();
+ const canManageRoles = services.application?.capabilities.management?.security?.roles === true;
- return (
-
-
-
-
-
-
-
-
-
- {this.getDescription()}
-
-
-
-
-
-
- );
- }
-
- private getPanelTitle = () => {
- const featureCount = this.props.features.length;
- const enabledCount = getEnabledFeatures(this.props.features, this.props.space).length;
-
- let details: null | ReactNode = null;
-
- if (enabledCount === featureCount) {
- details = (
-
-
-
-
-
- );
- } else if (enabledCount === 0) {
- details = (
-
-
-
-
-
- );
- } else {
- details = (
-
-
-
-
-
- );
- }
-
- return (
-
- {' '}
- {details}
-
- );
- };
-
- private getDescription = () => {
- return (
-
-
-
-
-
-
-
- );
- };
-}
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss
deleted file mode 100644
index 35b9dc1d45661..0000000000000
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.spcFeatureTableAccordionContent {
- // Align accordion content with the feature category logo in the accordion's buttonContent
- padding-left: $euiSizeXL;
-}
diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx
index 2c9eaf4563d05..78ea73741a8ad 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx
@@ -5,18 +5,16 @@
* 2.0.
*/
-import './feature_table.scss';
-
import type { EuiCheckboxProps } from '@elastic/eui';
import {
EuiAccordion,
+ EuiButtonEmpty,
EuiCallOut,
EuiCheckbox,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiIcon,
- EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
@@ -88,9 +86,9 @@ export class FeatureTable extends Component {
const buttonContent = (
{
if (!canExpandCategory) {
const isChecked = enabledCount > 0;
@@ -101,31 +99,28 @@ export class FeatureTable extends Component {
}
}}
>
-
-
-
{category.euiIconType ? (
) : null}
-
- {category.label}
+
+ {category.label}
);
const label: string = i18n.translate('xpack.spaces.management.featureAccordionSwitchLabel', {
- defaultMessage: '{enabledCount} / {featureCount} features visible',
+ defaultMessage: '{enabledCount}/{featureCount} features visible',
values: {
enabledCount,
featureCount,
},
});
const extraAction = (
-
+
{label}
);
@@ -133,46 +128,50 @@ export class FeatureTable extends Component {
const helpText = this.getCategoryHelpText(category);
const accordion = (
-
-
-
- {helpText && (
- <>
-
- {helpText}
-
-
- >
- )}
- {featuresInCategory.map((feature) => {
- const featureChecked = !(
- space.disabledFeatures && space.disabledFeatures.includes(feature.id)
- );
-
- return (
-
-
-
-
-
- );
- })}
-
-
+
+
+
+
+
+
+
+ {helpText && (
+ <>
+
+ {helpText}
+
+
+ >
+ )}
+ {featuresInCategory.map((feature) => {
+ const featureChecked = !(
+ space.disabledFeatures && space.disabledFeatures.includes(feature.id)
+ );
+
+ return (
+
+
+
+
+
+ );
+ })}
+
+
+
);
accordions.push({
@@ -188,30 +187,34 @@ export class FeatureTable extends Component {
const controls = [];
if (enabledCount < featureCount) {
controls.push(
- this.showAll()} data-test-subj="showAllFeaturesLink">
-
- {i18n.translate('xpack.spaces.management.selectAllFeaturesLink', {
- defaultMessage: 'Select all',
- })}
-
-
+ this.showAll()}
+ size="xs"
+ data-test-subj="showAllFeaturesLink"
+ >
+ {i18n.translate('xpack.spaces.management.selectAllFeaturesLink', {
+ defaultMessage: 'Show all',
+ })}
+
);
}
if (enabledCount > 0) {
controls.push(
- this.hideAll()} data-test-subj="hideAllFeaturesLink">
-
- {i18n.translate('xpack.spaces.management.deselectAllFeaturesLink', {
- defaultMessage: 'Deselect all',
- })}
-
-
+ this.hideAll()}
+ size="xs"
+ data-test-subj="hideAllFeaturesLink"
+ >
+ {i18n.translate('xpack.spaces.management.deselectAllFeaturesLink', {
+ defaultMessage: 'Hide all',
+ })}
+
);
}
return (
-
+
@@ -227,10 +230,10 @@ export class FeatureTable extends Component {
))}
-
+
{accordions.flatMap((a, idx) => [
a.element,
- ,
+ ,
])}
);
diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
index 79daeba2c8ec3..06d6298239c09 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx
@@ -55,7 +55,6 @@ describe('ManageSpacePage', () => {
});
});
- const getUrlForApp = (appId: string) => appId;
const history = scopedHistoryMock.create();
it('allows a space to be created', async () => {
@@ -68,7 +67,6 @@ describe('ManageSpacePage', () => {
spacesManager={(spacesManager as unknown) as SpacesManager}
getFeatures={featuresStart.getFeatures}
notifications={notificationServiceMock.createStartContract()}
- getUrlForApp={getUrlForApp}
history={history}
capabilities={{
navLinks: {},
@@ -98,8 +96,9 @@ describe('ManageSpacePage', () => {
id: 'new-space-name',
name: 'New Space Name',
description: 'some description',
- color: undefined,
- initials: undefined,
+ initials: 'NS',
+ color: '#AA6556',
+ imageUrl: '',
disabledFeatures: [],
});
});
@@ -129,7 +128,6 @@ describe('ManageSpacePage', () => {
onLoadSpace={onLoadSpace}
getFeatures={featuresStart.getFeatures}
notifications={notificationServiceMock.createStartContract()}
- getUrlForApp={getUrlForApp}
history={history}
capabilities={{
navLinks: {},
@@ -161,8 +159,9 @@ describe('ManageSpacePage', () => {
id: 'existing-space',
name: 'New Space Name',
description: 'some description',
- color: '#aabbcc',
+ color: '#AABBCC',
initials: 'AB',
+ imageUrl: '',
disabledFeatures: ['feature-1'],
});
});
@@ -181,7 +180,6 @@ describe('ManageSpacePage', () => {
spacesManager={(spacesManager as unknown) as SpacesManager}
getFeatures={() => Promise.reject(error)}
notifications={notifications}
- getUrlForApp={getUrlForApp}
history={history}
capabilities={{
navLinks: {},
@@ -218,7 +216,6 @@ describe('ManageSpacePage', () => {
spacesManager={(spacesManager as unknown) as SpacesManager}
getFeatures={featuresStart.getFeatures}
notifications={notificationServiceMock.createStartContract()}
- getUrlForApp={getUrlForApp}
history={history}
capabilities={{
navLinks: {},
@@ -279,7 +276,6 @@ describe('ManageSpacePage', () => {
spacesManager={(spacesManager as unknown) as SpacesManager}
getFeatures={featuresStart.getFeatures}
notifications={notificationServiceMock.createStartContract()}
- getUrlForApp={getUrlForApp}
history={history}
capabilities={{
navLinks: {},
diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx
index 5d9ef3e1316ff..5a7ae701e97e0 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx
@@ -11,27 +11,25 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiPageContent,
+ EuiPageContentBody,
EuiPageHeader,
EuiSpacer,
- EuiTitle,
+ hexToHsv,
+ hsvToHex,
} from '@elastic/eui';
-import _ from 'lodash';
-import React, { Component, Fragment } from 'react';
+import { difference } from 'lodash';
+import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import type {
- ApplicationStart,
- Capabilities,
- NotificationsStart,
- ScopedHistory,
-} from 'src/core/public';
+import type { Capabilities, NotificationsStart, ScopedHistory } from 'src/core/public';
import type { Space } from 'src/plugins/spaces_oss/common';
import { SectionLoading } from '../../../../../../src/plugins/es_ui_shared/public';
import type { FeaturesPluginStart, KibanaFeature } from '../../../../features/public';
import { isReservedSpace } from '../../../common';
import { getSpacesFeatureDescription } from '../../constants';
+import { getSpaceColor, getSpaceInitials } from '../../space_avatar';
import type { SpacesManager } from '../../spaces_manager';
import { UnauthorizedPrompt } from '../components';
import { toSpaceIdentifier } from '../lib';
@@ -40,7 +38,13 @@ 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 { ReservedSpaceBadge } from './reserved_space_badge';
+
+export interface FormValues extends Partial {
+ customIdentifier?: boolean;
+ avatarType?: 'initials' | 'image';
+ customAvatarInitials?: boolean;
+ customAvatarColor?: boolean;
+}
interface Props {
getFeatures: FeaturesPluginStart['getFeatures'];
@@ -50,11 +54,10 @@ interface Props {
onLoadSpace?: (space: Space) => void;
capabilities: Capabilities;
history: ScopedHistory;
- getUrlForApp: ApplicationStart['getUrlForApp'];
}
interface State {
- space: Partial;
+ space: FormValues;
features: KibanaFeature[];
originalSpace?: Partial;
showAlteringActiveSpaceDialog: boolean;
@@ -76,7 +79,9 @@ export class ManageSpacePage extends Component {
isLoading: true,
showAlteringActiveSpaceDialog: false,
saveInProgress: false,
- space: {},
+ space: {
+ color: getSpaceColor({}),
+ },
features: [],
};
}
@@ -124,16 +129,12 @@ export class ManageSpacePage extends Component {
}
return (
-
-
+
+
{this.getForm()}
-
+
);
}
@@ -166,7 +167,6 @@ export class ManageSpacePage extends Component {
space={this.state.space}
features={this.state.features}
onChange={this.onSpaceChange}
- getUrlForApp={this.props.getUrlForApp}
/>
@@ -185,27 +185,19 @@ export class ManageSpacePage extends Component {
);
};
- public getFormHeading = () => (
-
-
-
- {this.getTitle()}
-
-
-
-
-
-
- );
-
public getTitle = () => {
if (this.editingExistingSpace()) {
- return `Edit space`;
+ return (
+
+ );
}
return (
);
};
@@ -274,7 +266,7 @@ export class ManageSpacePage extends Component {
return null;
};
- public onSpaceChange = (updatedSpace: Partial) => {
+ public onSpaceChange = (updatedSpace: FormValues) => {
this.setState({
space: updatedSpace,
});
@@ -283,7 +275,9 @@ export class ManageSpacePage extends Component {
public saveSpace = () => {
this.validator.enableValidation();
- const result = this.validator.validateForSave(this.state.space as Space);
+ const originalSpace: Space = this.state.originalSpace as Space;
+ const space: Space = this.state.space as Space;
+ const result = this.validator.validateForSave(space);
if (result.isInvalid) {
this.setState({
formError: result,
@@ -295,15 +289,12 @@ export class ManageSpacePage extends Component {
if (this.editingExistingSpace()) {
const { spacesManager } = this.props;
- const originalSpace: Space = this.state.originalSpace as Space;
- const space: Space = this.state.space as Space;
-
spacesManager.getActiveSpace().then((activeSpace) => {
const editingActiveSpace = activeSpace.id === originalSpace.id;
const haveDisabledFeaturesChanged =
space.disabledFeatures.length !== originalSpace.disabledFeatures.length ||
- _.difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0;
+ difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0;
if (editingActiveSpace && haveDisabledFeaturesChanged) {
this.setState({
@@ -333,7 +324,14 @@ export class ManageSpacePage extends Component {
}
this.setState({
- space,
+ space: {
+ ...space,
+ avatarType: space.imageUrl ? 'image' : 'initials',
+ initials: space.initials || getSpaceInitials(space),
+ customIdentifier: false,
+ customAvatarInitials: getSpaceInitials({ name: space.name }) !== space.initials,
+ customAvatarColor: getSpaceColor({ name: space.name }) !== space.color,
+ },
features,
originalSpace: space,
isLoading: false,
@@ -365,16 +363,17 @@ export class ManageSpacePage extends Component {
color,
disabledFeatures = [],
imageUrl,
+ avatarType,
} = this.state.space;
const params = {
name,
id,
description,
- initials,
- color,
+ initials: avatarType !== 'image' ? initials : '',
+ color: color ? hsvToHex(hexToHsv(color)).toUpperCase() : color, // Convert 3 digit hex codes to 6 digits since Spaces API requires 6 digits
disabledFeatures,
- imageUrl,
+ imageUrl: avatarType === 'image' ? imageUrl : '',
};
let action;
diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap
index 233a054e057f3..630de56f3bbdc 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap
@@ -14,7 +14,7 @@ exports[`it renders without blowing up 1`] = `
grow={false}
>
{
const wrapper = shallowWithIntl(
-
+
child
);
@@ -23,7 +23,7 @@ test('it renders without blowing up', () => {
test('it renders children', () => {
const wrapper = mountWithIntl(
-
+
child 1
child 2
diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
index 2eb6c5ea377f6..4ffa5ae9de2ea 100644
--- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
+++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx
@@ -13,7 +13,6 @@ import React, { Component, Fragment } from 'react';
interface Props {
iconType?: IconType;
title: string | ReactNode;
- description: string;
}
export class SectionPanel extends Component {
@@ -30,7 +29,7 @@ export class SectionPanel extends Component {
return (
-
+
{this.props.iconType && (
diff --git a/x-pack/plugins/spaces/public/management/lib/validate_space.test.ts b/x-pack/plugins/spaces/public/management/lib/validate_space.test.ts
index faadad9812050..ddf38c9098d1b 100644
--- a/x-pack/plugins/spaces/public/management/lib/validate_space.test.ts
+++ b/x-pack/plugins/spaces/public/management/lib/validate_space.test.ts
@@ -9,20 +9,20 @@ import { SpaceValidator } from './validate_space';
let validator: SpaceValidator;
-describe('validateSpaceName', () => {
- beforeEach(() => {
- validator = new SpaceValidator({
- shouldValidate: true,
- });
+beforeEach(() => {
+ validator = new SpaceValidator({
+ shouldValidate: true,
});
+});
+describe('validateSpaceName', () => {
test('it allows a name with special characters', () => {
const space = {
id: '',
name: 'This is the name of my Space! @#$%^&*()_+-=',
};
- expect(validator.validateSpaceName(space)).toEqual({ isInvalid: false });
+ expect(validator.validateSpaceName(space)).toHaveProperty('isInvalid', false);
});
test('it requires a non-empty value', () => {
@@ -31,10 +31,7 @@ describe('validateSpaceName', () => {
name: '',
};
- expect(validator.validateSpaceName(space)).toEqual({
- isInvalid: true,
- error: `Name is required.`,
- });
+ expect(validator.validateSpaceName(space)).toHaveProperty('isInvalid', true);
});
test('it cannot be composed entirely of whitespace', () => {
@@ -43,10 +40,7 @@ describe('validateSpaceName', () => {
name: ' ',
};
- expect(validator.validateSpaceName(space)).toEqual({
- isInvalid: true,
- error: `Name is required.`,
- });
+ expect(validator.validateSpaceName(space)).toHaveProperty('isInvalid', true);
});
test('it cannot exceed 1024 characters', () => {
@@ -55,10 +49,7 @@ describe('validateSpaceName', () => {
name: new Array(1026).join('A'),
};
- expect(validator.validateSpaceName(space)).toEqual({
- isInvalid: true,
- error: `Name must not exceed 1024 characters.`,
- });
+ expect(validator.validateSpaceName(space)).toHaveProperty('isInvalid', true);
});
});
@@ -69,7 +60,7 @@ describe('validateSpaceDescription', () => {
name: '',
};
- expect(validator.validateSpaceDescription(space)).toEqual({ isInvalid: false });
+ expect(validator.validateSpaceDescription(space)).toHaveProperty('isInvalid', false);
});
test('it cannot exceed 2000 characters', () => {
@@ -79,10 +70,7 @@ describe('validateSpaceDescription', () => {
description: new Array(2002).join('A'),
};
- expect(validator.validateSpaceDescription(space)).toEqual({
- isInvalid: true,
- error: `Description must not exceed 2000 characters.`,
- });
+ expect(validator.validateSpaceDescription(space)).toHaveProperty('isInvalid', true);
});
});
@@ -94,7 +82,7 @@ describe('validateURLIdentifier', () => {
_reserved: true,
};
- expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: false });
+ expect(validator.validateURLIdentifier(space)).toHaveProperty('isInvalid', false);
});
test('it requires a non-empty value', () => {
@@ -103,10 +91,7 @@ describe('validateURLIdentifier', () => {
name: '',
};
- expect(validator.validateURLIdentifier(space)).toEqual({
- isInvalid: true,
- error: `URL identifier is required.`,
- });
+ expect(validator.validateURLIdentifier(space)).toHaveProperty('isInvalid', true);
});
test('it requires a valid Space Identifier', () => {
@@ -115,10 +100,7 @@ describe('validateURLIdentifier', () => {
name: '',
};
- expect(validator.validateURLIdentifier(space)).toEqual({
- isInvalid: true,
- error: 'URL identifier can only contain a-z, 0-9, and the characters "_" and "-".',
- });
+ expect(validator.validateURLIdentifier(space)).toHaveProperty('isInvalid', true);
});
test('it allows a valid Space Identifier', () => {
@@ -131,6 +113,95 @@ describe('validateURLIdentifier', () => {
});
});
+describe('validateAvatarInitials', () => {
+ it('it allows valid initials', () => {
+ const space = {
+ initials: 'FF',
+ };
+
+ expect(validator.validateAvatarInitials(space)).toHaveProperty('isInvalid', false);
+ });
+
+ it('it requires a non-empty value', () => {
+ const space = {
+ initials: '',
+ };
+
+ expect(validator.validateAvatarInitials(space)).toHaveProperty('isInvalid', true);
+ });
+
+ it('must not exceed 2 characters', () => {
+ const space = {
+ initials: 'FFF',
+ };
+
+ expect(validator.validateAvatarInitials(space)).toHaveProperty('isInvalid', true);
+ });
+
+ it('it does not validate image avatars', () => {
+ const space = {
+ avatarType: 'image' as 'image',
+ initials: '',
+ };
+
+ expect(validator.validateAvatarInitials(space)).toHaveProperty('isInvalid', false);
+ });
+});
+
+describe('validateAvatarColor', () => {
+ it('it allows valid colors', () => {
+ const space = {
+ color: '#000000',
+ };
+
+ expect(validator.validateAvatarColor(space)).toHaveProperty('isInvalid', false);
+ });
+
+ it('it requires a non-empty value', () => {
+ const space = {
+ color: '',
+ };
+
+ expect(validator.validateAvatarColor(space)).toHaveProperty('isInvalid', true);
+ });
+
+ it('it requires a valid hex code', () => {
+ const space = {
+ color: 'red',
+ };
+
+ expect(validator.validateAvatarColor(space)).toHaveProperty('isInvalid', true);
+ });
+});
+
+describe('validateAvatarImage', () => {
+ it('it allows valid image url', () => {
+ const space = {
+ avatarType: 'image' as 'image',
+ imageUrl: 'foo',
+ };
+
+ expect(validator.validateAvatarImage(space)).toHaveProperty('isInvalid', false);
+ });
+
+ it('it requires a non-empty value', () => {
+ const space = {
+ avatarType: 'image' as 'image',
+ imageUrl: '',
+ };
+
+ expect(validator.validateAvatarImage(space)).toHaveProperty('isInvalid', true);
+ });
+
+ it('it does not validate non-image avatars', () => {
+ const space = {
+ imageUrl: '',
+ };
+
+ expect(validator.validateAvatarImage(space)).toHaveProperty('isInvalid', false);
+ });
+});
+
describe('validateSpaceFeatures', () => {
it('allows features to be disabled', () => {
const space = {
@@ -139,7 +210,7 @@ describe('validateSpaceFeatures', () => {
disabledFeatures: ['foo'],
};
- expect(validator.validateEnabledFeatures(space)).toEqual({ isInvalid: false });
+ expect(validator.validateEnabledFeatures(space)).toHaveProperty('isInvalid', false);
});
it('allows all features to be disabled', () => {
@@ -149,8 +220,6 @@ describe('validateSpaceFeatures', () => {
disabledFeatures: ['foo', 'bar'],
};
- expect(validator.validateEnabledFeatures(space)).toEqual({
- isInvalid: false,
- });
+ expect(validator.validateEnabledFeatures(space)).toHaveProperty('isInvalid', false);
});
});
diff --git a/x-pack/plugins/spaces/public/management/lib/validate_space.ts b/x-pack/plugins/spaces/public/management/lib/validate_space.ts
index 157e990ef66ca..4767d35f060fb 100644
--- a/x-pack/plugins/spaces/public/management/lib/validate_space.ts
+++ b/x-pack/plugins/spaces/public/management/lib/validate_space.ts
@@ -5,10 +5,12 @@
* 2.0.
*/
+import { isValidHex } from '@elastic/eui';
+
import { i18n } from '@kbn/i18n';
-import type { Space } from 'src/plugins/spaces_oss/common';
import { isReservedSpace } from '../../../common/is_reserved_space';
+import type { FormValues } from '../edit_space/manage_space_page';
import { isValidSpaceIdentifier } from './space_identifier_utils';
interface SpaceValidatorOptions {
@@ -30,7 +32,7 @@ export class SpaceValidator {
this.shouldValidate = false;
}
- public validateSpaceName(space: Partial) {
+ public validateSpaceName(space: FormValues) {
if (!this.shouldValidate) {
return valid();
}
@@ -38,7 +40,7 @@ export class SpaceValidator {
if (!space.name || !space.name.trim()) {
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.requiredNameErrorMessage', {
- defaultMessage: 'Name is required.',
+ defaultMessage: 'Enter a name.',
})
);
}
@@ -54,7 +56,7 @@ export class SpaceValidator {
return valid();
}
- public validateSpaceDescription(space: Partial) {
+ public validateSpaceDescription(space: FormValues) {
if (!this.shouldValidate) {
return valid();
}
@@ -70,7 +72,7 @@ export class SpaceValidator {
return valid();
}
- public validateURLIdentifier(space: Partial) {
+ public validateURLIdentifier(space: FormValues) {
if (!this.shouldValidate) {
return valid();
}
@@ -82,7 +84,7 @@ export class SpaceValidator {
if (!space.id) {
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.urlIdentifierRequiredErrorMessage', {
- defaultMessage: 'URL identifier is required.',
+ defaultMessage: 'Enter a URL identifier.',
})
);
}
@@ -102,17 +104,93 @@ export class SpaceValidator {
return valid();
}
- public validateEnabledFeatures(space: Partial) {
+ public validateAvatarInitials(space: FormValues) {
+ if (!this.shouldValidate) {
+ return valid();
+ }
+
+ if (space.avatarType !== 'image') {
+ if (!space.initials) {
+ return invalid(
+ i18n.translate('xpack.spaces.management.validateSpace.requiredInitialsErrorMessage', {
+ defaultMessage: 'Enter initials.',
+ })
+ );
+ }
+ if (space.initials.length > 2) {
+ return invalid(
+ i18n.translate('xpack.spaces.management.validateSpace.maxLengthInitialsErrorMessage', {
+ defaultMessage: 'Enter no more than 2 characters.',
+ })
+ );
+ }
+ }
+
+ return valid();
+ }
+
+ public validateAvatarColor(space: FormValues) {
+ if (!this.shouldValidate) {
+ return valid();
+ }
+
+ if (!space.color) {
+ return invalid(
+ i18n.translate('xpack.spaces.management.validateSpace.requiredColorErrorMessage', {
+ defaultMessage: 'Select a background color.',
+ })
+ );
+ }
+
+ if (!isValidHex(space.color)) {
+ return invalid(
+ i18n.translate('xpack.spaces.management.validateSpace.invalidColorErrorMessage', {
+ defaultMessage: 'Enter a valid HEX color code.',
+ })
+ );
+ }
+
+ return valid();
+ }
+
+ public validateAvatarImage(space: FormValues) {
+ if (!this.shouldValidate) {
+ return valid();
+ }
+
+ if (space.avatarType === 'image' && !space.imageUrl) {
+ return invalid(
+ i18n.translate('xpack.spaces.management.validateSpace.requiredImageErrorMessage', {
+ defaultMessage: 'Upload an image.',
+ })
+ );
+ }
+
+ return valid();
+ }
+
+ public validateEnabledFeatures(space: FormValues) {
return valid();
}
- public validateForSave(space: Space) {
+ public validateForSave(space: FormValues) {
const { isInvalid: isNameInvalid } = this.validateSpaceName(space);
const { isInvalid: isDescriptionInvalid } = this.validateSpaceDescription(space);
const { isInvalid: isIdentifierInvalid } = this.validateURLIdentifier(space);
+ const { isInvalid: isAvatarInitialsInvalid } = this.validateAvatarInitials(space);
+ const { isInvalid: isAvatarColorInvalid } = this.validateAvatarColor(space);
+ const { isInvalid: isAvatarImageInvalid } = this.validateAvatarImage(space);
const { isInvalid: areFeaturesInvalid } = this.validateEnabledFeatures(space);
- if (isNameInvalid || isDescriptionInvalid || isIdentifierInvalid || areFeaturesInvalid) {
+ if (
+ isNameInvalid ||
+ isDescriptionInvalid ||
+ isIdentifierInvalid ||
+ isAvatarInitialsInvalid ||
+ isAvatarColorInvalid ||
+ isAvatarImageInvalid ||
+ areFeaturesInvalid
+ ) {
return invalid();
}
diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_page.test.tsx.snap b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_page.test.tsx.snap
index b61012f0cd33a..5cc23fe79a03f 100644
--- a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_page.test.tsx.snap
+++ b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_page.test.tsx.snap
@@ -24,7 +24,7 @@ exports[`SpacesGridPage renders as expected 1`] = `
onClick={[Function]}
>
diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx
index 1e3fde827b050..200b868a1b103 100644
--- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx
@@ -153,7 +153,7 @@ export class SpacesGridPage extends Component {
>
);
diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
index 9e419862893be..0b6df121cb166 100644
--- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
@@ -102,7 +102,7 @@ describe('spacesManagementApp', () => {
expect(setBreadcrumbs).toHaveBeenCalledTimes(1);
expect(setBreadcrumbs).toHaveBeenCalledWith([
{ href: `/`, text: 'Spaces' },
- { text: 'Create' },
+ { href: '/create', text: 'Create' },
]);
expect(docTitle.change).toHaveBeenCalledWith('Spaces');
expect(docTitle.reset).not.toHaveBeenCalled();
diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
index 12573ce1eac1b..4bf8b46fecb75 100644
--- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx
@@ -81,6 +81,7 @@ export const spacesManagementApp = Object.freeze({
text: i18n.translate('xpack.spaces.management.createSpaceBreadcrumb', {
defaultMessage: 'Create',
}),
+ href: '/create',
},
]);
@@ -91,7 +92,6 @@ export const spacesManagementApp = Object.freeze({
notifications={notifications}
spacesManager={spacesManager}
history={history}
- getUrlForApp={application.getUrlForApp}
/>
);
};
@@ -118,7 +118,6 @@ export const spacesManagementApp = Object.freeze({
spaceId={spaceId}
onLoadSpace={onLoadSpace}
history={history}
- getUrlForApp={application.getUrlForApp}
/>
);
};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index eed481aff3c24..9756c38e65f6e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -21918,35 +21918,17 @@
"xpack.spaces.management.copyToSpaceFlyoutFooter.successCount": "コピー完了",
"xpack.spaces.management.copyToSpaceFlyoutHeader": "スペースにコピー",
"xpack.spaces.management.createSpaceBreadcrumb": "作成",
- "xpack.spaces.management.customizeSpaceAvatar.colorFormRowLabel": "色",
- "xpack.spaces.management.customizeSpaceAvatar.imageUrl": "カスタム画像",
- "xpack.spaces.management.customizeSpaceAvatar.initialItemsFormRowLabel": "イニシャル (最大 2 文字) ",
- "xpack.spaces.management.customizeSpaceAvatar.removeImage": "カスタム画像を削除",
- "xpack.spaces.management.customizeSpaceAvatar.selectImageUrl": "画像ファイルを選択",
"xpack.spaces.management.deleteSpacesButton.deleteSpaceAriaLabel": "スペースを削除",
"xpack.spaces.management.deleteSpacesButton.deleteSpaceButtonLabel": "スペースを削除",
"xpack.spaces.management.deselectAllFeaturesLink": "すべて選択解除",
"xpack.spaces.management.enabledFeatures.featureCategoryButtonLabel": "カテゴリ切り替え",
- "xpack.spaces.management.enabledSpaceFeatures.allFeaturesEnabledMessage": " (表示されているすべての機能) ",
- "xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage": "機能",
- "xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage": "このスペースの機能の表示を設定",
- "xpack.spaces.management.enabledSpaceFeatures.noFeaturesEnabledMessage": " (表示されている機能がありません) ",
- "xpack.spaces.management.enabledSpaceFeatures.notASecurityMechanismMessage": "この機能は UI で非表示になっていますが、無効ではありません。",
- "xpack.spaces.management.enabledSpaceFeatures.someFeaturesEnabledMessage": " ({featureCount} 件中 {enabledCount} 件の機能を表示中) ",
"xpack.spaces.management.featureAccordionSwitchLabel": "{featureCount}件中{enabledCount}件の機能を表示中",
"xpack.spaces.management.featureVisibilityTitle": "機能の表示",
"xpack.spaces.management.hideAllFeaturesText": "すべて非表示",
"xpack.spaces.management.managementCategoryHelpText": "スタック管理へのアクセスは割り当てられた権限によって決まり、スペースで非表示にすることはできません。",
- "xpack.spaces.management.manageSpacePage.avatarFormRowLabel": "アバター",
- "xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder": "素晴らしいスペース",
"xpack.spaces.management.manageSpacePage.cancelSpaceButton": "キャンセル",
"xpack.spaces.management.manageSpacePage.createSpaceButton": "スペースを作成",
"xpack.spaces.management.manageSpacePage.createSpaceTitle": "スペースの作成",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelDescription": "スペースに名前を付けてアバターをカスタマイズします。",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable": "URL 識別子に注意してください。スペースの作成後に変更することはできません。",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable": "URL 識別子は変更できません。",
- "xpack.spaces.management.manageSpacePage.customizeSpaceTitle": "スペースのカスタマイズ",
- "xpack.spaces.management.manageSpacePage.customizeVisibleFeatures": "表示される機能のカスタマイズ",
"xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle": "スペースの読み込み中にエラーが発生:{message}",
"xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle": "スペースの保存中にエラーが発生:{message}",
"xpack.spaces.management.manageSpacePage.loadErrorTitle": "利用可能な機能の読み込みエラー",
@@ -21958,15 +21940,6 @@
"xpack.spaces.management.reversedSpaceBadge.reversedSpacesCanBePartiallyModifiedTooltip": "リザーブされたスペースはビルトインのため、部分的な変更しかできません。",
"xpack.spaces.management.selectAllFeaturesLink": "すべて選択",
"xpack.spaces.management.showAllFeaturesText": "すべて表示",
- "xpack.spaces.management.spaceIdentifier.customizeSpaceLinkText": "[カスタマイズ]",
- "xpack.spaces.management.spaceIdentifier.customizeSpaceNameLinkLabel": "URL 識別子をカスタマイズ",
- "xpack.spaces.management.spaceIdentifier.emptySpaceIdentifierText": "awesome-space",
- "xpack.spaces.management.spaceIdentifier.kibanaURLForSpaceIdentifierDescription": "例:https://my-kibana.example{spaceIdentifier}/app/kibana.",
- "xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkLabel": "URL 識別子をリセット",
- "xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkText": "[リセット]",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierGeneratedFromSpaceNameTooltip": "awesome-space",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierLabel": "URL 識別子 ",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierTitle": "URL 識別子",
"xpack.spaces.management.spacesGridPage.actionsColumnName": "アクション",
"xpack.spaces.management.spacesGridPage.allFeaturesEnabled": "表示されているすべての機能",
"xpack.spaces.management.spacesGridPage.createSpaceButtonLabel": "スペースの作成",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index cc288ebecdc74..f1f985406fbd0 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -22272,35 +22272,17 @@
"xpack.spaces.management.copyToSpaceFlyoutFooter.successCount": "已复制",
"xpack.spaces.management.copyToSpaceFlyoutHeader": "复制到工作区",
"xpack.spaces.management.createSpaceBreadcrumb": "创建",
- "xpack.spaces.management.customizeSpaceAvatar.colorFormRowLabel": "颜色",
- "xpack.spaces.management.customizeSpaceAvatar.imageUrl": "定制图像",
- "xpack.spaces.management.customizeSpaceAvatar.initialItemsFormRowLabel": "名字缩写 (最多两个字符) ",
- "xpack.spaces.management.customizeSpaceAvatar.removeImage": "删除定制图像",
- "xpack.spaces.management.customizeSpaceAvatar.selectImageUrl": "选择图像文件",
"xpack.spaces.management.deleteSpacesButton.deleteSpaceAriaLabel": "删除此空间",
"xpack.spaces.management.deleteSpacesButton.deleteSpaceButtonLabel": "删除空间",
"xpack.spaces.management.deselectAllFeaturesLink": "取消全选",
"xpack.spaces.management.enabledFeatures.featureCategoryButtonLabel": "类别切换",
- "xpack.spaces.management.enabledSpaceFeatures.allFeaturesEnabledMessage": " (所有可见功能) ",
- "xpack.spaces.management.enabledSpaceFeatures.enabledFeaturesSectionMessage": "功能",
- "xpack.spaces.management.enabledSpaceFeatures.enableFeaturesInSpaceMessage": "为此工作区设置功能可见性",
- "xpack.spaces.management.enabledSpaceFeatures.noFeaturesEnabledMessage": " (没有可见功能) ",
- "xpack.spaces.management.enabledSpaceFeatures.notASecurityMechanismMessage": "该功能在 UI 中已隐藏,但未禁用。",
- "xpack.spaces.management.enabledSpaceFeatures.someFeaturesEnabledMessage": " ({enabledCount} / {featureCount} 个功能可见) ",
"xpack.spaces.management.featureAccordionSwitchLabel": "{enabledCount} / {featureCount} 个功能可见",
"xpack.spaces.management.featureVisibilityTitle": "功能可见性",
"xpack.spaces.management.hideAllFeaturesText": "全部隐藏",
"xpack.spaces.management.managementCategoryHelpText": "对堆栈管理的访问由您的权限决定,并且不能被工作区隐藏。",
- "xpack.spaces.management.manageSpacePage.avatarFormRowLabel": "头像",
- "xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder": "超卓的空间",
"xpack.spaces.management.manageSpacePage.cancelSpaceButton": "取消",
"xpack.spaces.management.manageSpacePage.createSpaceButton": "创建工作区",
"xpack.spaces.management.manageSpacePage.createSpaceTitle": "创建一个空间",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelDescription": "命名您的工作区并定制其头像。",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierEditable": "记下 URL 标识符。创建工作区后,将不能更改它。",
- "xpack.spaces.management.manageSpacePage.customizeSpacePanelUrlIdentifierNotEditable": "URL 标识符无法更改。",
- "xpack.spaces.management.manageSpacePage.customizeSpaceTitle": "定制您的工作区",
- "xpack.spaces.management.manageSpacePage.customizeVisibleFeatures": "定制可见功能",
"xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle": "加载空间时出错:{message}",
"xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle": "保存空间时出错:{message}",
"xpack.spaces.management.manageSpacePage.loadErrorTitle": "加载可用功能时出错",
@@ -22312,15 +22294,6 @@
"xpack.spaces.management.reversedSpaceBadge.reversedSpacesCanBePartiallyModifiedTooltip": "保留的工作区是内置的,只能进行部分修改。",
"xpack.spaces.management.selectAllFeaturesLink": "全选",
"xpack.spaces.management.showAllFeaturesText": "全部显示",
- "xpack.spaces.management.spaceIdentifier.customizeSpaceLinkText": "[定制]",
- "xpack.spaces.management.spaceIdentifier.customizeSpaceNameLinkLabel": "定制 URL 标识符",
- "xpack.spaces.management.spaceIdentifier.emptySpaceIdentifierText": "awesome-space",
- "xpack.spaces.management.spaceIdentifier.kibanaURLForSpaceIdentifierDescription": "示例:https://my-kibana.example{spaceIdentifier}/app/kibana。",
- "xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkLabel": "重置 URL 标识符",
- "xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkText": "[重置]",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierGeneratedFromSpaceNameTooltip": "awesome-space",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierLabel": "URL 标识符 ",
- "xpack.spaces.management.spaceIdentifier.urlIdentifierTitle": "URL 标识符",
"xpack.spaces.management.spacesGridPage.actionsColumnName": "操作",
"xpack.spaces.management.spacesGridPage.allFeaturesEnabled": "所有可见功能",
"xpack.spaces.management.spacesGridPage.createSpaceButtonLabel": "创建一个空间",
@@ -22340,11 +22313,6 @@
"xpack.spaces.management.toggleAllFeaturesLink": " (全部更改) ",
"xpack.spaces.management.unauthorizedPrompt.permissionDeniedDescription": "您无权管理工作区。",
"xpack.spaces.management.unauthorizedPrompt.permissionDeniedTitle": "权限被拒绝",
- "xpack.spaces.management.validateSpace.describeMaxLengthErrorMessage": "描述不能超过 2000 个字符。",
- "xpack.spaces.management.validateSpace.nameMaxLengthErrorMessage": "名称不能超过 1024 个字符。",
- "xpack.spaces.management.validateSpace.requiredNameErrorMessage": "“名称”必填。",
- "xpack.spaces.management.validateSpace.urlIdentifierAllowedCharactersErrorMessage": "URL 标识符只能包含 a-z、0-9 和字符“_”及“-”。",
- "xpack.spaces.management.validateSpace.urlIdentifierRequiredErrorMessage": "“URL 标识符”必填。",
"xpack.spaces.manageSpacesButton.manageSpacesButtonLabel": "管理空间",
"xpack.spaces.navControl.spacesMenu.changeCurrentSpaceTitle": "更改当前空间",
"xpack.spaces.navControl.spacesMenu.findSpacePlaceholder": "查找工作区",
diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts
index afd9295d9e6b8..b85364bb84766 100644
--- a/x-pack/test/accessibility/apps/spaces.ts
+++ b/x-pack/test/accessibility/apps/spaces.ts
@@ -46,15 +46,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('a11y test for click on create space page', async () => {
await PageObjects.spaceSelector.clickCreateSpace();
- await a11y.testAppSnapshot();
- });
-
- it('a11y test for for customize space card', async () => {
await PageObjects.spaceSelector.clickEnterSpaceName();
await PageObjects.spaceSelector.addSpaceName('space_a');
- await PageObjects.spaceSelector.clickCustomizeSpaceAvatar('space_a');
await a11y.testAppSnapshot();
- await browser.pressKeys(browser.keys.ESCAPE);
});
// EUI issue - https://github.com/elastic/eui/issues/3999
@@ -64,18 +58,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await browser.pressKeys(browser.keys.ESCAPE);
});
- it('a11y test for customize and reset space URL identifier', async () => {
- await PageObjects.spaceSelector.clickOnCustomizeURL();
- await a11y.testAppSnapshot();
- await PageObjects.spaceSelector.clickOnCustomizeURL();
- await a11y.testAppSnapshot();
- });
-
- it('a11y test for describe space text space', async () => {
- await PageObjects.spaceSelector.clickOnDescriptionOfSpace();
- await a11y.testAppSnapshot();
- });
-
it('a11y test for toggling an entire feature category', async () => {
await PageObjects.spaceSelector.toggleFeatureCategoryVisibility('kibana');
await a11y.testAppSnapshot();