Translate Spaces component (#24411)

* Translate Spaces component

* fix one little error

* update Spaces component

* update translation of Spaces components

* Update snapshots

* update Space translation - intl type

* update Space translation - remove view/views id namespace

* rename ids

* use testing helper functions instead of shallow, render, mount from enzyme

* fix unit tests

* fix ts path for enzyme test helpers

* fix path to enzyme helpers test functions

* Update snapshots

* fix path to enzyme test helpers

* Remove unused dependency.
This commit is contained in:
tibmt 2018-11-15 11:09:12 +03:00 committed by Maryia Lapata
parent 1155b81b4f
commit fb6be4caed
46 changed files with 797 additions and 219 deletions

View file

@ -24,7 +24,8 @@ export function createJestConfig({
"^ui/(.*)": `${kibanaDirectory}/src/ui/public/$1`,
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
`${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`,
"\\.(css|less|scss)$": `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`
"\\.(css|less|scss)$": `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`,
"^test_utils/enzyme_helpers": `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`
},
setupFiles: [
`${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`,

View file

@ -5,8 +5,8 @@
*/
import { EuiFlyout, EuiLink } from '@elastic/eui';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ImpactedSpacesFlyout } from './impacted_spaces_flyout';
import { PrivilegeSpaceTable } from './privilege_space_table';
@ -52,16 +52,16 @@ const buildProps = (customProps = {}) => {
describe('<ImpactedSpacesFlyout>', () => {
it('renders without crashing', () => {
expect(shallow(<ImpactedSpacesFlyout {...buildProps()} />)).toMatchSnapshot();
expect(shallowWithIntl(<ImpactedSpacesFlyout {...buildProps()} />)).toMatchSnapshot();
});
it('does not immediately show the flyout', () => {
const wrapper = mount(<ImpactedSpacesFlyout {...buildProps()} />);
const wrapper = mountWithIntl(<ImpactedSpacesFlyout {...buildProps()} />);
expect(wrapper.find(EuiFlyout)).toHaveLength(0);
});
it('shows the flyout after clicking the link', () => {
const wrapper = mount(<ImpactedSpacesFlyout {...buildProps()} />);
const wrapper = mountWithIntl(<ImpactedSpacesFlyout {...buildProps()} />);
wrapper.find(EuiLink).simulate('click');
expect(wrapper.find(EuiFlyout)).toHaveLength(1);
});
@ -82,7 +82,7 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallow(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);
@ -112,7 +112,7 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallow(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);
@ -141,7 +141,7 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallow(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);

View file

@ -11,6 +11,10 @@ exports[`ManageSpacesButton renders as expected 1`] = `
size="s"
type="button"
>
Manage spaces
<FormattedMessage
defaultMessage="Manage spaces"
id="xpack.spaces.manageSpacesButton.manageSpacesButtonLabel"
values={Object {}}
/>
</EuiButton>
`;

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { UserProfileProvider } from '../../../xpack_main/public/services/user_profile';
import { ManageSpacesButton } from './manage_spaces_button';
@ -15,11 +15,11 @@ const buildUserProfile = (canManageSpaces: boolean) => {
describe('ManageSpacesButton', () => {
it('renders as expected', () => {
const component = <ManageSpacesButton userProfile={buildUserProfile(true)} />;
expect(shallow(component)).toMatchSnapshot();
expect(shallowWithIntl(component)).toMatchSnapshot();
});
it(`doesn't render if user profile forbids managing spaces`, () => {
const component = <ManageSpacesButton userProfile={buildUserProfile(false)} />;
expect(shallow(component)).toMatchSnapshot();
expect(shallowWithIntl(component)).toMatchSnapshot();
});
});

View file

@ -5,6 +5,7 @@
*/
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, CSSProperties } from 'react';
import { UserProfile } from '../../../xpack_main/public/services/user_profile';
import { MANAGE_SPACES_URL } from '../lib/constants';
@ -32,7 +33,10 @@ export class ManageSpacesButton extends Component<Props, {}> {
onClick={this.navigateToManageSpaces}
style={this.props.style}
>
Manage spaces
<FormattedMessage
id="xpack.spaces.manageSpacesButton.manageSpacesButtonLabel"
defaultMessage="Manage spaces"
/>
</EuiButton>
);
}

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { IHttpResponse } from 'angular';
@ -66,8 +67,12 @@ export class SpacesManager extends EventEmitter {
public _displayError() {
toastNotifications.addDanger({
title: 'Unable to change your Space',
text: 'please try again later',
title: i18n.translate('xpack.spaces.spacesManager.unableToChangeSpaceWarningTitle', {
defaultMessage: 'Unable to change your Space',
}),
text: i18n.translate('xpack.spaces.spacesManager.unableToChangeSpaceWarningDescription', {
defaultMessage: 'please try again later',
}),
});
}
}

View file

@ -11,8 +11,15 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
<EuiModalHeaderTitle
data-test-subj="confirmModalTitleText"
>
Delete space
'My Space'
<FormattedMessage
defaultMessage="Delete space {spaceName}"
id="xpack.spaces.management.confirmDeleteModal.confirmDeleteSpaceButtonLabel"
values={
Object {
"spaceName": "'My Space'",
}
}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
@ -22,12 +29,21 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
size="m"
>
<p>
Deleting a space permanently removes the space and
<strong>
all of its contents
</strong>
. You can't undo this action.
<FormattedMessage
defaultMessage="Deleting a space permanently removes the space and {allContents}. You can't undo this action."
id="xpack.spaces.management.confirmDeleteModal.deletingSpaceWarningMessage"
values={
Object {
"allContents": <strong>
<FormattedMessage
defaultMessage="all of its contents"
id="xpack.spaces.management.confirmDeleteModal.allContentsText"
values={Object {}}
/>
</strong>,
}
}
/>
</p>
<EuiFormRow
describedByIds={Array []}
@ -54,15 +70,21 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
grow={true}
size="m"
>
You are about to delete your current space
<span>
(
<strong>
My Space
</strong>
)
</span>
. You will be redirected to choose a different space if you continue.
<FormattedMessage
defaultMessage="You are about to delete your current space {name}. You will be redirected to choose a different space if you continue."
id="xpack.spaces.management.confirmDeleteModal.redirectAfterDeletingCurrentSpaceWarningMessage"
values={
Object {
"name": <span>
(
<strong>
My Space
</strong>
)
</span>,
}
}
/>
</EuiText>
</EuiCallOut>
</EuiText>
@ -76,7 +98,11 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
onClick={[MockFunction]}
type="button"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
id="xpack.spaces.management.confirmDeleteModal.cancelButtonLabel"
values={Object {}}
/>
</EuiButtonEmpty>
<EuiButton
color="danger"
@ -87,7 +113,11 @@ exports[`ConfirmDeleteModal renders as expected 1`] = `
onClick={[Function]}
type="button"
>
Delete space and all contents
<FormattedMessage
defaultMessage=" Delete space and all contents"
id="xpack.spaces.management.confirmDeleteModal.deleteSpaceAndAllContentsButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>

View file

@ -6,14 +6,22 @@ exports[`UnauthorizedPrompt renders as expected 1`] = `
<p
data-test-subj="permissionDeniedMessage"
>
You do not have permission to manage spaces.
<FormattedMessage
defaultMessage="You do not have permission to manage spaces."
id="xpack.spaces.management.unauthorizedPrompt.permissionDeniedDescription"
values={Object {}}
/>
</p>
}
iconColor="danger"
iconType="spacesApp"
title={
<h2>
Permission denied
<FormattedMessage
defaultMessage="Permission denied"
id="xpack.spaces.management.unauthorizedPrompt.permissionDeniedTitle"
values={Object {}}
/>
</h2>
}
/>

View file

@ -11,11 +11,17 @@ exports[`AdvancedSettingsSubtitle renders as expected 1`] = `
size="m"
title={
<p>
The settings on this page apply to the
<strong>
My Space
</strong>
space, unless otherwise specified.
<FormattedMessage
defaultMessage="The settings on this page apply to the {spaceName} space, unless otherwise specified."
id="xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription"
values={
Object {
"spaceName": <strong>
My Space
</strong>,
}
}
/>
</p>
}
/>

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { AdvancedSettingsSubtitle } from './advanced_settings_subtitle';
describe('AdvancedSettingsSubtitle', () => {
@ -13,6 +13,6 @@ describe('AdvancedSettingsSubtitle', () => {
id: 'my-space',
name: 'My Space',
};
expect(shallow(<AdvancedSettingsSubtitle space={space} />)).toMatchSnapshot();
expect(shallowWithIntl(<AdvancedSettingsSubtitle space={space} />)).toMatchSnapshot();
});
});

View file

@ -5,6 +5,7 @@
*/
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import { Space } from '../../../../../common/model/space';
@ -20,8 +21,13 @@ export const AdvancedSettingsSubtitle = (props: Props) => (
iconType="spacesApp"
title={
<p>
The settings on this page apply to the <strong>{props.space.name}</strong> space, unless
otherwise specified.
<FormattedMessage
id="xpack.spaces.management.advancedSettingsSubtitle.applyingSettingsOnPageToSpaceDescription"
defaultMessage="The settings on this page apply to the {spaceName} space, unless otherwise specified."
values={{
spaceName: <strong>{props.space.name}</strong>,
}}
/>
</p>
}
/>

View file

@ -39,7 +39,11 @@ exports[`AdvancedSettingsTitle renders as expected 1`] = `
<h1
data-test-subj="managementSettingsTitle"
>
Settings
<FormattedMessage
defaultMessage="Settings"
id="xpack.spaces.management.advancedSettingsTitle.settingsTitle"
values={Object {}}
/>
</h1>
</EuiText>
</EuiFlexItem>

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { AdvancedSettingsTitle } from './advanced_settings_title';
describe('AdvancedSettingsTitle', () => {
@ -13,6 +13,6 @@ describe('AdvancedSettingsTitle', () => {
id: 'my-space',
name: 'My Space',
};
expect(shallow(<AdvancedSettingsTitle space={space} />)).toMatchSnapshot();
expect(shallowWithIntl(<AdvancedSettingsTitle space={space} />)).toMatchSnapshot();
});
});

View file

@ -5,6 +5,7 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { Space } from '../../../../../common/model/space';
import { SpaceAvatar } from '../../../../components';
@ -20,7 +21,12 @@ export const AdvancedSettingsTitle = (props: Props) => (
</EuiFlexItem>
<EuiFlexItem style={{ marginLeft: '10px' }}>
<EuiText>
<h1 data-test-subj="managementSettingsTitle">Settings</h1>
<h1 data-test-subj="managementSettingsTitle">
<FormattedMessage
id="xpack.spaces.management.advancedSettingsTitle.settingsTitle"
defaultMessage="Settings"
/>
</h1>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { SpacesManager } from '../../../lib';
import { SpacesNavState } from '../../nav_control';
import { ConfirmDeleteModal } from './confirm_delete_modal';
@ -38,13 +38,14 @@ describe('ConfirmDeleteModal', () => {
const onConfirm = jest.fn();
expect(
shallow(
<ConfirmDeleteModal
shallowWithIntl(
<ConfirmDeleteModal.WrappedComponent
space={space}
spacesManager={spacesManager}
spacesNavState={spacesNavState}
onCancel={onCancel}
onConfirm={onConfirm}
intl={null as any}
/>
)
).toMatchSnapshot();
@ -71,13 +72,14 @@ describe('ConfirmDeleteModal', () => {
const onCancel = jest.fn();
const onConfirm = jest.fn();
const wrapper = mount(
<ConfirmDeleteModal
const wrapper = mountWithIntl(
<ConfirmDeleteModal.WrappedComponent
space={space}
spacesManager={spacesManager}
spacesNavState={spacesNavState}
onCancel={onCancel}
onConfirm={onConfirm}
intl={null as any}
/>
);

View file

@ -20,6 +20,7 @@ import {
EuiOverlayMask,
EuiText,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { SpacesNavState } from 'plugins/spaces/views/nav_control';
import React, { ChangeEvent, Component } from 'react';
import { Space } from '../../../../common/model/space';
@ -31,6 +32,7 @@ interface Props {
spacesNavState: SpacesNavState;
onCancel: () => void;
onConfirm: () => void;
intl: InjectedIntl;
}
interface State {
@ -39,7 +41,7 @@ interface State {
deleteInProgress: boolean;
}
export class ConfirmDeleteModal extends Component<Props, State> {
class ConfirmDeleteModalUI extends Component<Props, State> {
public state = {
confirmSpaceName: '',
error: null,
@ -47,7 +49,7 @@ export class ConfirmDeleteModal extends Component<Props, State> {
};
public render() {
const { space, spacesNavState, onCancel } = this.props;
const { space, spacesNavState, onCancel, intl } = this.props;
let warning = null;
if (isDeletingCurrentSpace(space, spacesNavState)) {
@ -59,8 +61,11 @@ export class ConfirmDeleteModal extends Component<Props, State> {
warning = (
<EuiCallOut color="warning">
<EuiText>
You are about to delete your current space {name}. You will be redirected to choose a
different space if you continue.
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.redirectAfterDeletingCurrentSpaceWarningMessage"
defaultMessage="You are about to delete your current space {name}. You will be redirected to choose a different space if you continue."
values={{ name }}
/>
</EuiText>
</EuiCallOut>
);
@ -74,20 +79,44 @@ export class ConfirmDeleteModal extends Component<Props, State> {
<EuiModal onClose={onCancel} className={'spcConfirmDeleteModal'}>
<EuiModalHeader>
<EuiModalHeaderTitle data-test-subj="confirmModalTitleText">
Delete space {`'${space.name}'`}
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.confirmDeleteSpaceButtonLabel"
defaultMessage="Delete space {spaceName}"
values={{
spaceName: "'" + space.name + "'",
}}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiText data-test-subj="confirmModalBodyText">
<p>
Deleting a space permanently removes the space and{' '}
<strong>all of its contents</strong>. You can't undo this action.
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.deletingSpaceWarningMessage"
defaultMessage="Deleting a space permanently removes the space and {allContents}. You can't undo this action."
values={{
allContents: (
<strong>
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.allContentsText"
defaultMessage="all of its contents"
/>
</strong>
),
}}
/>
</p>
<EuiFormRow
label={'Confirm space name'}
label={intl.formatMessage({
id: 'xpack.spaces.management.confirmDeleteModal.confirmSpaceNameFormRowLabel',
defaultMessage: 'Confirm space name',
})}
isInvalid={!!this.state.error}
error={'Space names do not match.'}
error={intl.formatMessage({
id: 'xpack.spaces.management.confirmDeleteModal.spaceNamesDoNoMatchErrorMessage',
defaultMessage: 'Space names do not match.',
})}
>
<EuiFieldText
value={this.state.confirmSpaceName}
@ -105,7 +134,10 @@ export class ConfirmDeleteModal extends Component<Props, State> {
onClick={onCancel}
isDisabled={this.state.deleteInProgress}
>
Cancel
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
<EuiButton
@ -115,7 +147,10 @@ export class ConfirmDeleteModal extends Component<Props, State> {
color={'danger'}
isLoading={this.state.deleteInProgress}
>
Delete space and all contents
<FormattedMessage
id="xpack.spaces.management.confirmDeleteModal.deleteSpaceAndAllContentsButtonLabel"
defaultMessage=" Delete space and all contents"
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>
@ -165,3 +200,5 @@ export class ConfirmDeleteModal extends Component<Props, State> {
function isDeletingCurrentSpace(space: Space, spacesNavState: SpacesNavState) {
return space.id === spacesNavState.getActiveSpace().id;
}
export const ConfirmDeleteModal = injectI18n(ConfirmDeleteModalUI);

View file

@ -13,16 +13,25 @@ exports[`SecureSpaceMessage renders if user profile allows security to be manage
size="m"
>
<p>
Want to assign a role to a space? Go to Management and select
<EuiLink
color="primary"
href="#/management/security/roles"
type="button"
>
Roles
</EuiLink>
.
<FormattedMessage
defaultMessage="Want to assign a role to a space? Go to Management and select {rolesLink}."
id="xpack.spaces.management.secureSpaceMessage.howToAssignRoleToSpaceDescription"
values={
Object {
"rolesLink": <EuiLink
color="primary"
href="#/management/security/roles"
type="button"
>
<FormattedMessage
defaultMessage="Roles"
id="xpack.spaces.management.secureSpaceMessage.rolesLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</p>
</EuiText>
</React.Fragment>

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { SecureSpaceMessage } from './secure_space_message';
describe('SecureSpaceMessage', () => {
@ -18,7 +18,7 @@ describe('SecureSpaceMessage', () => {
},
};
expect(shallow(<SecureSpaceMessage userProfile={userProfile} />)).toMatchSnapshot();
expect(shallowWithIntl(<SecureSpaceMessage userProfile={userProfile} />)).toMatchSnapshot();
});
it(`renders if user profile allows security to be managed`, () => {
@ -31,6 +31,6 @@ describe('SecureSpaceMessage', () => {
},
};
expect(shallow(<SecureSpaceMessage userProfile={userProfile} />)).toMatchSnapshot();
expect(shallowWithIntl(<SecureSpaceMessage userProfile={userProfile} />)).toMatchSnapshot();
});
});

View file

@ -5,6 +5,7 @@
*/
import { EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { UserProfile } from 'plugins/xpack_main/services/user_profile';
import React, { Fragment } from 'react';
@ -19,8 +20,20 @@ export const SecureSpaceMessage = (props: Props) => {
<EuiSpacer />
<EuiText className="eui-textCenter">
<p>
Want to assign a role to a space? Go to Management and select{' '}
<EuiLink href="#/management/security/roles">Roles</EuiLink>.
<FormattedMessage
id="xpack.spaces.management.secureSpaceMessage.howToAssignRoleToSpaceDescription"
defaultMessage="Want to assign a role to a space? Go to Management and select {rolesLink}."
values={{
rolesLink: (
<EuiLink href="#/management/security/roles">
<FormattedMessage
id="xpack.spaces.management.secureSpaceMessage.rolesLinkText"
defaultMessage="Roles"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
</Fragment>

View file

@ -3,12 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { UnauthorizedPrompt } from './unauthorized_prompt';
describe('UnauthorizedPrompt', () => {
it('renders as expected', () => {
expect(shallow(<UnauthorizedPrompt />)).toMatchSnapshot();
expect(shallowWithIntl(<UnauthorizedPrompt />)).toMatchSnapshot();
});
});

View file

@ -5,15 +5,28 @@
*/
import { EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
export const UnauthorizedPrompt = () => (
<EuiEmptyPrompt
iconType="spacesApp"
iconColor={'danger'}
title={<h2>Permission denied</h2>}
title={
<h2>
<FormattedMessage
id="xpack.spaces.management.unauthorizedPrompt.permissionDeniedTitle"
defaultMessage="Permission denied"
/>
</h2>
}
body={
<p data-test-subj="permissionDeniedMessage">You do not have permission to manage spaces.</p>
<p data-test-subj="permissionDeniedMessage">
<FormattedMessage
id="xpack.spaces.management.unauthorizedPrompt.permissionDeniedDescription"
defaultMessage="You do not have permission to manage spaces."
/>
</p>
}
/>
);

View file

@ -16,7 +16,11 @@ exports[`renders without crashing 1`] = `
onClick={[Function]}
type="button"
>
Customize
<FormattedMessage
defaultMessage="Customize"
id="xpack.spaces.management.customizeSpaceAvatar.customizeLinkText"
values={Object {}}
/>
</EuiLink>
</EuiFormRow>
</EuiFlexItem>

View file

@ -10,7 +10,11 @@ exports[`DeleteSpacesButton renders as expected 1`] = `
onClick={[Function]}
type="button"
>
Delete space
<FormattedMessage
defaultMessage="Delete space"
id="xpack.spaces.management.deleteSpacesButton.deleteSpaceButtonLabel"
values={Object {}}
/>
</EuiButton>
</React.Fragment>
`;

View file

@ -8,29 +8,50 @@ exports[`renders without crashing 1`] = `
hasEmptyLabelSpace={false}
helpText={
<p>
If the identifier is
<strong>
engineering
</strong>
, the Kibana URL is
<br />
https://my-kibana.example
<strong>
/s/engineering/
</strong>
app/kibana.
<FormattedMessage
defaultMessage="If the identifier is {engineeringIdentifier}, the Kibana URL is{nextLine}
{engineeringKibanaUrl}."
id="xpack.spaces.management.spaceIdentifier.kibanaURLForEngineeringIdentifierDescription"
values={
Object {
"engineeringIdentifier": <strong>
<FormattedMessage
defaultMessage="engineering"
id="xpack.spaces.management.spaceIdentifier.engineeringText"
values={Object {}}
/>
</strong>,
"engineeringKibanaUrl": <React.Fragment>
https://my-kibana.example
<strong>
/s/engineering/
</strong>
app/kibana
</React.Fragment>,
"nextLine": <br />,
}
}
/>
</p>
}
isInvalid={false}
label={
<p>
URL identifier
<FormattedMessage
defaultMessage="URL identifier"
id="xpack.spaces.management.spaceIdentifier.urlIdentifierLabel"
values={Object {}}
/>
<EuiLink
color="primary"
onClick={[Function]}
type="button"
>
[edit]
<FormattedMessage
defaultMessage="[edit]"
id="xpack.spaces.management.spaceIdentifier.editSpaceLinkText"
values={Object {}}
/>
</EuiLink>
</p>
}

View file

@ -5,8 +5,8 @@
*/
// @ts-ignore
import { EuiColorPicker, EuiFieldText, EuiLink } from '@elastic/eui';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { CustomizeSpaceAvatar } from './customize_space_avatar';
const space = {
@ -15,17 +15,23 @@ const space = {
};
test('renders without crashing', () => {
const wrapper = shallow(<CustomizeSpaceAvatar space={space} onChange={jest.fn()} />);
const wrapper = shallowWithIntl(
<CustomizeSpaceAvatar.WrappedComponent space={space} onChange={jest.fn()} intl={null as any} />
);
expect(wrapper).toMatchSnapshot();
});
test('renders a "customize" link by default', () => {
const wrapper = mount(<CustomizeSpaceAvatar space={space} onChange={jest.fn()} />);
const wrapper = mountWithIntl(
<CustomizeSpaceAvatar.WrappedComponent space={space} onChange={jest.fn()} intl={null as any} />
);
expect(wrapper.find(EuiLink)).toHaveLength(1);
});
test('shows customization fields when the "customize" link is clicked', () => {
const wrapper = mount(<CustomizeSpaceAvatar space={space} onChange={jest.fn()} />);
const wrapper = mountWithIntl(
<CustomizeSpaceAvatar.WrappedComponent space={space} onChange={jest.fn()} intl={null as any} />
);
wrapper.find(EuiLink).simulate('click');
expect(wrapper.find(EuiLink)).toHaveLength(0);
@ -43,7 +49,13 @@ test('invokes onChange callback when avatar is customized', () => {
const changeHandler = jest.fn();
const wrapper = mount(<CustomizeSpaceAvatar space={customizedSpace} onChange={changeHandler} />);
const wrapper = mountWithIntl(
<CustomizeSpaceAvatar.WrappedComponent
space={customizedSpace}
onChange={changeHandler}
intl={null as any}
/>
);
wrapper.find(EuiLink).simulate('click');
wrapper

View file

@ -5,6 +5,7 @@
*/
// @ts-ignore
import { EuiColorPicker, EuiFieldText, EuiFlexItem, EuiFormRow, EuiLink } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, Fragment } from 'react';
import { MAX_SPACE_INITIALS } from '../../../../common/constants';
import { Space } from '../../../../common/model/space';
@ -13,6 +14,7 @@ import { getSpaceColor, getSpaceInitials } from '../../../../common/space_attrib
interface Props {
space: Partial<Space>;
onChange: (space: Partial<Space>) => void;
intl: InjectedIntl;
}
interface State {
@ -21,7 +23,7 @@ interface State {
pendingInitials?: string | null;
}
export class CustomizeSpaceAvatar extends Component<Props, State> {
class CustomizeSpaceAvatarUI extends Component<Props, State> {
private initialsRef: HTMLInputElement | null = null;
constructor(props: Props) {
@ -37,14 +39,19 @@ export class CustomizeSpaceAvatar extends Component<Props, State> {
}
public getCustomizeFields = () => {
const { space } = this.props;
const { space, intl } = this.props;
const { initialsHasFocus, pendingInitials } = this.state;
return (
<Fragment>
<EuiFlexItem grow={false}>
<EuiFormRow label={'Initials (2 max)'}>
<EuiFormRow
label={intl.formatMessage({
id: 'xpack.spaces.management.customizeSpaceAvatar.initialItemsFormRowLabel',
defaultMessage: 'Initials (2 max)',
})}
>
<EuiFieldText
inputRef={this.initialsInputRef}
name="spaceInitials"
@ -56,7 +63,12 @@ export class CustomizeSpaceAvatar extends Component<Props, State> {
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiFormRow label={'Color'}>
<EuiFormRow
label={intl.formatMessage({
id: 'xpack.spaces.management.customizeSpaceAvatar.colorFormRowLabel',
defaultMessage: 'Color',
})}
>
<EuiColorPicker color={getSpaceColor(space)} onChange={this.onColorChange} />
</EuiFormRow>
</EuiFlexItem>
@ -97,7 +109,10 @@ export class CustomizeSpaceAvatar extends Component<Props, State> {
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace={true}>
<EuiLink name="customize_space_link" onClick={this.showFields}>
Customize
<FormattedMessage
id="xpack.spaces.management.customizeSpaceAvatar.customizeLinkText"
defaultMessage="Customize"
/>
</EuiLink>
</EuiFormRow>
</EuiFlexItem>
@ -130,3 +145,5 @@ export class CustomizeSpaceAvatar extends Component<Props, State> {
});
};
}
export const CustomizeSpaceAvatar = injectI18n(CustomizeSpaceAvatarUI);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { SpacesManager } from '../../../lib';
import { SpacesNavState } from '../../nav_control';
import { DeleteSpacesButton } from './delete_spaces_button';
@ -34,12 +34,13 @@ describe('DeleteSpacesButton', () => {
refreshSpacesList: jest.fn(),
};
const wrapper = shallow(
<DeleteSpacesButton
const wrapper = shallowWithIntl(
<DeleteSpacesButton.WrappedComponent
space={space}
spacesManager={spacesManager}
spacesNavState={spacesNavState}
onDelete={jest.fn()}
intl={null as any}
/>
);
expect(wrapper).toMatchSnapshot();

View file

@ -5,6 +5,7 @@
*/
import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { SpacesNavState } from 'plugins/spaces/views/nav_control';
import React, { Component, Fragment } from 'react';
// @ts-ignore
@ -19,6 +20,7 @@ interface Props {
spacesManager: SpacesManager;
spacesNavState: SpacesNavState;
onDelete: () => void;
intl: InjectedIntl;
}
interface State {
@ -26,14 +28,20 @@ interface State {
showConfirmRedirectModal: boolean;
}
export class DeleteSpacesButton extends Component<Props, State> {
class DeleteSpacesButtonUI extends Component<Props, State> {
public state = {
showConfirmDeleteModal: false,
showConfirmRedirectModal: false,
};
public render() {
const buttonText = `Delete space`;
const buttonText = (
<FormattedMessage
id="xpack.spaces.management.deleteSpacesButton.deleteSpaceButtonLabel"
defaultMessage="Delete space"
/>
);
const { intl } = this.props;
let ButtonComponent: any = EuiButton;
@ -49,7 +57,10 @@ export class DeleteSpacesButton extends Component<Props, State> {
<ButtonComponent
color={'danger'}
onClick={this.onDeleteClick}
aria-label={'Delete this space'}
aria-label={intl.formatMessage({
id: 'xpack.spaces.management.deleteSpacesButton.deleteSpaceAriaLabel',
defaultMessage: 'Delete this space',
})}
{...extraProps}
>
{buttonText}
@ -88,21 +99,40 @@ export class DeleteSpacesButton extends Component<Props, State> {
};
public deleteSpaces = async () => {
const { spacesManager, space, spacesNavState } = this.props;
const { spacesManager, space, spacesNavState, intl } = this.props;
try {
await spacesManager.deleteSpace(space);
} catch (error) {
const { message: errorMessage = '' } = error.data || {};
toastNotifications.addDanger(`Error deleting space: ${errorMessage}`);
toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.spaces.management.deleteSpacesButton.deleteSpaceErrorTitle',
defaultMessage: 'Error deleting space: {errorMessage}',
},
{
errorMessage,
}
)
);
}
this.setState({
showConfirmDeleteModal: false,
});
const message = `Deleted "${space.name}" space.`;
const message = intl.formatMessage(
{
id:
'xpack.spaces.management.deleteSpacesButton.spaceSuccessfullyDeletedNotificationMessage',
defaultMessage: 'Deleted {spaceName} space.',
},
{
spaceName: space.name,
}
);
toastNotifications.addSuccess(message);
@ -113,3 +143,5 @@ export class DeleteSpacesButton extends Component<Props, State> {
spacesNavState.refreshSpacesList();
};
}
export const DeleteSpacesButton = injectI18n(DeleteSpacesButtonUI);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount } from 'enzyme';
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { UserProfileProvider } from '../../../../../xpack_main/public/services/user_profile';
import { SpacesManager } from '../../../lib';
import { SpacesNavState } from '../../nav_control';
@ -42,11 +42,12 @@ describe('ManageSpacePage', () => {
const userProfile = buildUserProfile(true);
const wrapper = mount(
<ManageSpacePage
const wrapper = mountWithIntl(
<ManageSpacePage.WrappedComponent
spacesManager={spacesManager}
userProfile={userProfile}
spacesNavState={spacesNavState}
intl={null as any}
/>
);
const nameInput = wrapper.find('input[name="name"]');
@ -96,12 +97,13 @@ describe('ManageSpacePage', () => {
const userProfile = buildUserProfile(true);
const wrapper = mount(
<ManageSpacePage
const wrapper = mountWithIntl(
<ManageSpacePage.WrappedComponent
spaceId={'existing-space'}
spacesManager={spacesManager}
userProfile={userProfile}
spacesNavState={spacesNavState}
intl={null as any}
/>
);

View file

@ -21,6 +21,7 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, Fragment } from 'react';
import { SpacesNavState } from 'plugins/spaces/views/nav_control';
@ -45,6 +46,7 @@ interface Props {
spaceId?: string;
userProfile: UserProfile;
spacesNavState: SpacesNavState;
intl: InjectedIntl;
}
interface State {
@ -56,7 +58,7 @@ interface State {
};
}
export class ManageSpacePage extends Component<Props, State> {
class ManageSpacePageUI extends Component<Props, State> {
private readonly validator: SpaceValidator;
constructor(props: Props) {
@ -69,7 +71,7 @@ export class ManageSpacePage extends Component<Props, State> {
}
public componentDidMount() {
const { spaceId, spacesManager } = this.props;
const { spaceId, spacesManager, intl } = this.props;
if (spaceId) {
spacesManager
@ -85,7 +87,17 @@ export class ManageSpacePage extends Component<Props, State> {
.catch(error => {
const { message = '' } = error.data || {};
toastNotifications.addDanger(`Error loading space: ${message}`);
toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.spaces.management.manageSpacePage.errorLoadingSpaceTitle',
defaultMessage: 'Error loading space: {message}',
},
{
message,
}
)
);
this.backToSpacesList();
});
} else {
@ -113,14 +125,19 @@ export class ManageSpacePage extends Component<Props, State> {
<div>
<EuiLoadingSpinner size={'xl'} />{' '}
<EuiTitle>
<h1>Loading...</h1>
<h1>
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.loadingTitle"
defaultMessage="Loading…"
/>
</h1>
</EuiTitle>
</div>
);
};
public getForm = () => {
const { userProfile } = this.props;
const { userProfile, intl } = this.props;
if (!userProfile.hasCapability('manageSpaces')) {
return <UnauthorizedPrompt />;
@ -134,16 +151,25 @@ export class ManageSpacePage extends Component<Props, State> {
<EuiSpacer />
<EuiFormRow label="Name" {...this.validator.validateSpaceName(this.state.space)} fullWidth>
<EuiFormRow
label={intl.formatMessage({
id: 'xpack.spaces.management.manageSpacePage.nameFormRowLabel',
defaultMessage: 'Name',
})}
{...this.validator.validateSpaceName(this.state.space)}
fullWidth
>
<EuiFieldText
name="name"
placeholder={'Awesome space'}
placeholder={intl.formatMessage({
id: 'xpack.spaces.management.manageSpacePage.awesomeSpacePlaceholder',
defaultMessage: 'Awesome space',
})}
value={name}
onChange={this.onNameChange}
fullWidth
/>
</EuiFormRow>
{name && (
<Fragment>
<EuiFlexGroup responsive={false}>
@ -170,13 +196,19 @@ export class ManageSpacePage extends Component<Props, State> {
)}
<EuiFormRow
label="Description (optional)"
label={intl.formatMessage({
id: 'xpack.spaces.management.editSpace.manageSpacePage.optionalDescriptionFormRowLabel',
defaultMessage: 'Description (optional)',
})}
{...this.validator.validateSpaceDescription(this.state.space)}
fullWidth
>
<EuiFieldText
name="description"
placeholder={'This is where the magic happens'}
placeholder={intl.formatMessage({
id: 'xpack.spaces.management.manageSpacePage.hereMagicHappensPlaceholder',
defaultMessage: 'This is where the magic happens',
})}
value={description}
onChange={this.onDescriptionChange}
fullWidth
@ -202,9 +234,19 @@ export class ManageSpacePage extends Component<Props, State> {
public getTitle = () => {
if (this.editingExistingSpace()) {
return `Edit space`;
return (
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.editSpaceTitle"
defaultMessage="Edit space"
/>
);
}
return `Create space`;
return (
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.createSpaceTitle"
defaultMessage="Create space"
/>
);
};
public maybeGetSecureSpacesMessage = () => {
@ -215,7 +257,17 @@ export class ManageSpacePage extends Component<Props, State> {
};
public getFormButtons = () => {
const saveText = this.editingExistingSpace() ? 'Update space' : 'Create space';
const saveText = this.editingExistingSpace() ? (
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.updateSpaceButtonLabel"
defaultMessage="Update space"
/>
) : (
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.createSpaceButtonLabel"
defaultMessage="Create space"
/>
);
return (
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
@ -225,7 +277,10 @@ export class ManageSpacePage extends Component<Props, State> {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={this.backToSpacesList} data-test-subj="cancel-space-button">
Cancel
<FormattedMessage
id="xpack.spaces.management.manageSpacePage.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={true} />
@ -314,6 +369,7 @@ export class ManageSpacePage extends Component<Props, State> {
};
private performSave = () => {
const { intl } = this.props;
if (!this.state.space) {
return;
}
@ -339,13 +395,34 @@ export class ManageSpacePage extends Component<Props, State> {
action
.then(() => {
this.props.spacesNavState.refreshSpacesList();
toastNotifications.addSuccess(`'${name}' was saved`);
toastNotifications.addSuccess(
intl.formatMessage(
{
id:
'xpack.spaces.management.manageSpacePage.spaceSuccessfullySavedNotificationMessage',
defaultMessage: '{name} was saved',
},
{
name: `'${name}'`,
}
)
);
window.location.hash = `#/management/spaces/list`;
})
.catch(error => {
const { message = '' } = error.data || {};
toastNotifications.addDanger(`Error saving space: ${message}`);
toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.spaces.management.manageSpacePage.errorSavingSpaceTitle',
defaultMessage: 'Error saving space: {message}',
},
{
message,
}
)
);
});
};
@ -355,3 +432,5 @@ export class ManageSpacePage extends Component<Props, State> {
private editingExistingSpace = () => !!this.props.spaceId;
}
export const ManageSpacePage = injectI18n(ManageSpacePageUI);

View file

@ -5,8 +5,8 @@
*/
import { EuiIcon } from '@elastic/eui';
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ReservedSpaceBadge } from './reserved_space_badge';
const reservedSpace = {
@ -21,11 +21,11 @@ const unreservedSpace = {
};
test('it renders without crashing', () => {
const wrapper = shallow(<ReservedSpaceBadge space={reservedSpace} />);
const wrapper = shallowWithIntl(<ReservedSpaceBadge space={reservedSpace} />);
expect(wrapper.find(EuiIcon)).toHaveLength(1);
});
test('it renders nothing for an unreserved space', () => {
const wrapper = shallow(<ReservedSpaceBadge space={unreservedSpace} />);
const wrapper = shallowWithIntl(<ReservedSpaceBadge space={unreservedSpace} />);
expect(wrapper.find('*')).toHaveLength(0);
});

View file

@ -7,6 +7,7 @@
import React from 'react';
import { EuiIcon, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { isReservedSpace } from '../../../../common';
import { Space } from '../../../../common/model/space';
@ -19,7 +20,14 @@ export const ReservedSpaceBadge = (props: Props) => {
if (space && isReservedSpace(space)) {
return (
<EuiToolTip content={'Reserved spaces are built-in and can only be partially modified.'}>
<EuiToolTip
content={
<FormattedMessage
id="xpack.spaces.management.reversedSpaceBadge.reversedSpacesCanBePartiallyModifiedTooltip"
defaultMessage="Reserved spaces are built-in and can only be partially modified."
/>
}
>
<EuiIcon style={{ verticalAlign: 'super' }} type={'lock'} />
</EuiToolTip>
);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { SpaceValidator } from '../lib';
import { SpaceIdentifier } from './space_identifier';
@ -19,6 +19,8 @@ test('renders without crashing', () => {
onChange: jest.fn(),
validator: new SpaceValidator(),
};
const wrapper = shallow(<SpaceIdentifier {...props} />);
const wrapper = shallowWithIntl(
<SpaceIdentifier.WrappedComponent {...props} intl={null as any} />
);
expect(wrapper).toMatchSnapshot();
});

View file

@ -9,6 +9,7 @@ import {
EuiFormRow,
EuiLink,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, Fragment } from 'react';
import { Space } from '../../../../common/model/space';
import { SpaceValidator } from '../lib';
@ -18,13 +19,14 @@ interface Props {
editable: boolean;
validator: SpaceValidator;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
intl: InjectedIntl;
}
interface State {
editing: boolean;
}
export class SpaceIdentifier extends Component<Props, State> {
class SpaceIdentifierUI extends Component<Props, State> {
private textFieldRef: HTMLInputElement | null = null;
@ -36,6 +38,7 @@ export class SpaceIdentifier extends Component<Props, State> {
}
public render() {
const { intl } = this.props;
const {
id = ''
} = this.props.space;
@ -53,7 +56,11 @@ export class SpaceIdentifier extends Component<Props, State> {
placeholder={
this.state.editing || !this.props.editable
? undefined
: 'The URL identifier is generated from the space name.'
: intl.formatMessage({
id:
'xpack.spaces.management.spaceIdentifier.urlIdentifierGeneratedFromSpaceNameTooltip',
defaultMessage: 'The URL identifier is generated from the space name.',
})
}
value={id}
onChange={this.onChange}
@ -67,15 +74,63 @@ export class SpaceIdentifier extends Component<Props, State> {
public getLabel = () => {
if (!this.props.editable) {
return (<p>URL identifier</p>);
return (
<p>
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.urlIdentifierTitle"
defaultMessage="URL identifier"
/>
</p>);
}
const editLinkText = this.state.editing ? `[stop editing]` : `[edit]`;
return (<p>URL identifier <EuiLink onClick={this.onEditClick}>{editLinkText}</EuiLink></p>);
const editLinkText = this.state.editing ? (
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.stopEditingSpaceNameLinkText"
defaultMessage="[stop editing]"
/>
) : (
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.editSpaceLinkText"
defaultMessage="[edit]"
/>
);
return (
<p>
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.urlIdentifierLabel"
defaultMessage="URL identifier"
/>
<EuiLink onClick={this.onEditClick}>{editLinkText}</EuiLink>
</p>
);
};
public getHelpText = () => {
return (<p>If the identifier is <strong>engineering</strong>, the Kibana URL is <br /> https://my-kibana.example<strong>/s/engineering/</strong>app/kibana.</p>);
return (
<p>
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.kibanaURLForEngineeringIdentifierDescription"
defaultMessage="If the identifier is {engineeringIdentifier}, the Kibana URL is{nextLine}
{engineeringKibanaUrl}."
values={{
engineeringIdentifier: (
<strong>
<FormattedMessage
id="xpack.spaces.management.spaceIdentifier.engineeringText"
defaultMessage="engineering"
/>
</strong>
),
nextLine: <br />,
engineeringKibanaUrl: (
<React.Fragment>
https://my-kibana.example<strong>/s/engineering/</strong>app/kibana
</React.Fragment>
),
}}
/>
</p>
);
};
public onEditClick = () => {
@ -93,3 +148,5 @@ export class SpaceIdentifier extends Component<Props, State> {
this.props.onChange(e);
};
}
export const SpaceIdentifier = injectI18n(SpaceIdentifierUI);

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { isReservedSpace } from '../../../../common/is_reserved_space';
import { Space } from '../../../../common/model/space';
import { isValidSpaceIdentifier } from './space_identifier_utils';
@ -32,11 +33,19 @@ export class SpaceValidator {
}
if (!space.name || !space.name.trim()) {
return invalid(`Name is required`);
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.requiredNameErrorMessage', {
defaultMessage: 'Name is required',
})
);
}
if (space.name.length > 1024) {
return invalid(`Name must not exceed 1024 characters`);
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.nameMaxLengthErrorMessage', {
defaultMessage: 'Name must not exceed 1024 characters',
})
);
}
return valid();
@ -48,7 +57,11 @@ export class SpaceValidator {
}
if (space.description && space.description.length > 2000) {
return invalid(`Description must not exceed 2000 characters`);
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.describeMaxLengthErrorMessage', {
defaultMessage: 'Description must not exceed 2000 characters',
})
);
}
return valid();
@ -64,11 +77,23 @@ export class SpaceValidator {
}
if (!space.id) {
return invalid(`URL identifier is required`);
return invalid(
i18n.translate('xpack.spaces.management.validateSpace.urlIdentifierRequiredErrorMessage', {
defaultMessage: 'URL identifier is required',
})
);
}
if (!isValidSpaceIdentifier(space.id)) {
return invalid('URL identifier can only contain a-z, 0-9, and the characters "_" and "-"');
return invalid(
i18n.translate(
'xpack.spaces.management.validateSpace.urlIdentifierAllowedCharactersErrorMessage',
{
defaultMessage:
'URL identifier can only contain a-z, 0-9, and the characters "_" and "-"',
}
)
);
}
return valid();

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { I18nProvider } from '@kbn/i18n/react';
// @ts-ignore
import template from 'plugins/spaces/views/management/template.html';
@ -39,11 +40,13 @@ routes.when('/management/spaces/list', {
const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render(
<SpacesGridPage
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>,
<I18nProvider>
<SpacesGridPage
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>
</I18nProvider>,
domNode
);
@ -75,11 +78,13 @@ routes.when('/management/spaces/create', {
const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render(
<ManageSpacePage
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>,
<I18nProvider>
<ManageSpacePage
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>
</I18nProvider>,
domNode
);
@ -118,12 +123,14 @@ routes.when('/management/spaces/edit/:spaceId', {
const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render(
<ManageSpacePage
spaceId={spaceId}
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>,
<I18nProvider>
<ManageSpacePage
spaceId={spaceId}
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={userProfile}
/>
</I18nProvider>,
domNode
);

View file

@ -31,7 +31,11 @@ exports[`SpacesGridPage renders as expected 1`] = `
size="m"
>
<h1>
Spaces
<FormattedMessage
defaultMessage="Spaces"
id="xpack.spaces.management.spacesGridPage.spacesTitle"
values={Object {}}
/>
</h1>
</EuiText>
</EuiFlexItem>
@ -46,7 +50,11 @@ exports[`SpacesGridPage renders as expected 1`] = `
onClick={[Function]}
type="button"
>
Create space
<FormattedMessage
defaultMessage="Create space"
id="xpack.spaces.management.spacesGridPage.createSpaceButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
@ -107,7 +115,13 @@ exports[`SpacesGridPage renders as expected 1`] = `
itemId="id"
items={Array []}
loading={true}
message="loading..."
message={
<FormattedMessage
defaultMessage="loading…"
id="xpack.spaces.management.spacesGridPage.loadingTitle"
values={Object {}}
/>
}
pagination={true}
responsive={true}
search={

View file

@ -19,6 +19,7 @@ import {
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
// @ts-ignore
import { toastNotifications } from 'ui/notify';
@ -36,6 +37,7 @@ interface Props {
spacesManager: SpacesManager;
spacesNavState: SpacesNavState;
userProfile: UserProfile;
intl: InjectedIntl;
}
interface State {
@ -46,7 +48,7 @@ interface State {
error: Error | null;
}
export class SpacesGridPage extends Component<Props, State> {
class SpacesGridPageUI extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
@ -75,6 +77,7 @@ export class SpacesGridPage extends Component<Props, State> {
}
public getPageContent() {
const { intl } = this.props;
if (!this.props.userProfile.hasCapability('manageSpaces')) {
return <UnauthorizedPrompt />;
}
@ -84,7 +87,12 @@ export class SpacesGridPage extends Component<Props, State> {
<EuiFlexGroup justifyContent={'spaceBetween'}>
<EuiFlexItem grow={false}>
<EuiText>
<h1>Spaces</h1>
<h1>
<FormattedMessage
id="xpack.spaces.management.spacesGridPage.spacesTitle"
defaultMessage="Spaces"
/>
</h1>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>{this.getPrimaryActionButton()}</EuiFlexItem>
@ -99,11 +107,23 @@ export class SpacesGridPage extends Component<Props, State> {
pagination={true}
search={{
box: {
placeholder: 'Search',
placeholder: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.searchPlaceholder',
defaultMessage: 'Search',
}),
},
}}
loading={this.state.loading}
message={this.state.loading ? 'loading...' : undefined}
message={
this.state.loading ? (
<FormattedMessage
id="xpack.spaces.management.spacesGridPage.loadingTitle"
defaultMessage="loading…"
/>
) : (
undefined
)
}
/>
</Fragment>
);
@ -117,7 +137,10 @@ export class SpacesGridPage extends Component<Props, State> {
window.location.hash = `#/management/spaces/create`;
}}
>
Create space
<FormattedMessage
id="xpack.spaces.management.spacesGridPage.createSpaceButtonLabel"
defaultMessage="Create space"
/>
</EuiButton>
);
}
@ -145,6 +168,7 @@ export class SpacesGridPage extends Component<Props, State> {
};
public deleteSpace = async () => {
const { intl } = this.props;
const { spacesManager, spacesNavState } = this.props;
const space = this.state.selectedSpace;
@ -158,7 +182,17 @@ export class SpacesGridPage extends Component<Props, State> {
} catch (error) {
const { message: errorMessage = '' } = error.data || {};
toastNotifications.addDanger(`Error deleting space: ${errorMessage}`);
toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.spaces.management.spacesGridPage.errorDeletingSpaceErrorMessage',
defaultMessage: 'Error deleting space: {errorMessage}',
},
{
errorMessage,
}
)
);
}
this.setState({
@ -167,7 +201,15 @@ export class SpacesGridPage extends Component<Props, State> {
this.loadGrid();
const message = `Deleted "${space.name}" space.`;
const message = intl.formatMessage(
{
id: 'xpack.spaces.management.spacesGridPage.spaceSuccessfullyDeletedNotificationMessage',
defaultMessage: 'Deleted "{spaceName}" space.',
},
{
spaceName: space.name,
}
);
toastNotifications.addSuccess(message);
@ -203,6 +245,7 @@ export class SpacesGridPage extends Component<Props, State> {
};
public getColumnConfig() {
const { intl } = this.props;
return [
{
field: 'name',
@ -223,7 +266,10 @@ export class SpacesGridPage extends Component<Props, State> {
},
{
field: 'name',
name: 'Space',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.spaceColumnName',
defaultMessage: 'Space',
}),
sortable: true,
render: (value: string, record: Space) => {
return (
@ -239,20 +285,35 @@ export class SpacesGridPage extends Component<Props, State> {
},
{
field: 'id',
name: 'Identifier',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.identifierColumnName',
defaultMessage: 'Identifier',
}),
sortable: true,
},
{
field: 'description',
name: 'Description',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.descriptionColumnName',
defaultMessage: 'Description',
}),
sortable: true,
},
{
name: 'Actions',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.actionsColumnName',
defaultMessage: 'Actions',
}),
actions: [
{
name: 'Edit',
description: 'Edit this space.',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.editSpaceActionName',
defaultMessage: 'Edit',
}),
description: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.editSpaceActionDescription',
defaultMessage: 'Edit this space.',
}),
onClick: this.onEditSpaceClick,
type: 'icon',
icon: 'pencil',
@ -260,8 +321,14 @@ export class SpacesGridPage extends Component<Props, State> {
},
{
available: (record: Space) => !isReservedSpace(record),
name: 'Delete',
description: 'Delete this space.',
name: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.deleteActionName',
defaultMessage: 'Delete',
}),
description: intl.formatMessage({
id: 'xpack.spaces.management.spacesGridPage.deleteThisSpaceActionDescription',
defaultMessage: 'Delete this space.',
}),
onClick: this.onDeleteSpaceClick,
type: 'icon',
icon: 'trash',
@ -283,3 +350,5 @@ export class SpacesGridPage extends Component<Props, State> {
});
};
}
export const SpacesGridPage = injectI18n(SpacesGridPageUI);

View file

@ -3,8 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { UserProfileProvider } from '../../../../../xpack_main/public/services/user_profile';
import { SpaceAvatar } from '../../../components';
import { SpacesManager } from '../../../lib';
@ -54,22 +54,24 @@ const spacesManager = new SpacesManager(mockHttp, mockChrome, '');
describe('SpacesGridPage', () => {
it('renders as expected', () => {
expect(
shallow(
<SpacesGridPage
shallowWithIntl(
<SpacesGridPage.WrappedComponent
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={buildUserProfile(true)}
intl={null as any}
/>
)
).toMatchSnapshot();
});
it('renders the list of spaces', async () => {
const wrapper = mount(
<SpacesGridPage
const wrapper = mountWithIntl(
<SpacesGridPage.WrappedComponent
spacesManager={spacesManager}
spacesNavState={spacesNavState}
userProfile={buildUserProfile(true)}
intl={null as any}
/>
);

View file

@ -5,6 +5,7 @@
*/
import { EuiContextMenuItem, EuiContextMenuPanel, EuiFieldSearch, EuiText } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { UserProfile } from '../../../../../xpack_main/public/services/user_profile';
import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../common/constants';
@ -16,6 +17,7 @@ interface Props {
onSelectSpace: (space: Space) => void;
onManageSpacesClick: () => void;
userProfile: UserProfile;
intl: InjectedIntl;
}
interface State {
@ -23,20 +25,24 @@ interface State {
allowSpacesListFocus: boolean;
}
export class SpacesMenu extends Component<Props, State> {
class SpacesMenuUI extends Component<Props, State> {
public state = {
searchTerm: '',
allowSpacesListFocus: false,
};
public render() {
const { intl } = this.props;
const { searchTerm } = this.state;
const items = this.getVisibleSpaces(searchTerm).map(this.renderSpaceMenuItem);
const panelProps = {
className: 'spcMenu',
title: 'Change current space',
title: intl.formatMessage({
id: 'xpack.spaces.navControl.spacesMenu.changeCurrentSpaceTitle',
defaultMessage: 'Change current space',
}),
watchedItemProps: ['data-search-term'],
};
@ -76,8 +82,10 @@ export class SpacesMenu extends Component<Props, State> {
if (items.length === 0) {
return (
<EuiText color="subdued" className="eui-textCenter">
{' '}
no spaces found{' '}
<FormattedMessage
id="xpack.spaces.navControl.spacesMenu.noSpacesFoundTitle"
defaultMessage=" no spaces found "
/>
</EuiText>
);
}
@ -95,10 +103,14 @@ export class SpacesMenu extends Component<Props, State> {
};
private renderSearchField = () => {
const { intl } = this.props;
return (
<div key="manageSpacesSearchField" className="spcMenu__searchFieldWrapper">
<EuiFieldSearch
placeholder="Find a space"
placeholder={intl.formatMessage({
id: 'xpack.spaces.navControl.spacesMenu.findSpacePlaceholder',
defaultMessage: 'Find a space',
})}
incremental={true}
// FIXME needs updated typedef
// @ts-ignore
@ -165,3 +177,5 @@ export class SpacesMenu extends Component<Props, State> {
);
};
}
export const SpacesMenu = injectI18n(SpacesMenuUI);

View file

@ -5,6 +5,7 @@
*/
import { EuiAvatar, EuiPopover, PopoverAnchorPosition } from '@elastic/eui';
import { I18nProvider } from '@kbn/i18n/react';
import React, { Component, ComponentClass } from 'react';
import { UserProfile } from '../../../../xpack_main/public/services/user_profile';
import { Space } from '../../../common/model/space';
@ -70,12 +71,14 @@ export class NavControlPopover extends Component<Props, State> {
);
} else {
element = (
<SpacesMenu
spaces={this.state.spaces}
onSelectSpace={this.onSelectSpace}
userProfile={this.props.userProfile}
onManageSpacesClick={this.toggleSpaceSelector}
/>
<I18nProvider>
<SpacesMenu
spaces={this.state.spaces}
onSelectSpace={this.onSelectSpace}
userProfile={this.props.userProfile}
onManageSpacesClick={this.toggleSpaceSelector}
/>
</I18nProvider>
);
}

View file

@ -29,7 +29,11 @@ exports[`it renders without crashing 1`] = `
textTransform="none"
>
<h1>
Select your space
<FormattedMessage
defaultMessage="Select your space"
id="xpack.spaces.spaceSelector.selectSpacesTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
<EuiText
@ -38,7 +42,11 @@ exports[`it renders without crashing 1`] = `
size="s"
>
<p>
You can change your space at anytime
<FormattedMessage
defaultMessage="You can change your space at anytime"
id="xpack.spaces.spaceSelector.changeSpaceAnytimeAvailabilityText"
values={Object {}}
/>
</p>
</EuiText>
</EuiPageHeader>
@ -72,7 +80,11 @@ exports[`it renders without crashing 1`] = `
size="m"
textAlign="center"
>
No spaces match search criteria
<FormattedMessage
defaultMessage="No spaces match search criteria"
id="xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription"
values={Object {}}
/>
</EuiText>
</React.Fragment>
</EuiPageContent>

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { I18nProvider } from '@kbn/i18n/react';
import { SpacesManager } from 'plugins/spaces/lib/spaces_manager';
// @ts-ignore
import template from 'plugins/spaces/views/space_selector/space_selector.html';
@ -26,7 +27,12 @@ module.controller(
const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL);
render(<SpaceSelector spaces={spaces} spacesManager={spacesManager} />, domNode);
render(
<I18nProvider>
<SpaceSelector spaces={spaces} spacesManager={spacesManager} />
</I18nProvider>,
domNode
);
// unmount react on controller destroy
$scope.$on('$destroy', () => {

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { render, shallow } from 'enzyme';
import React from 'react';
import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import chrome from 'ui/chrome';
import { Space } from '../../../common/model/space';
import { SpacesManager } from '../../lib/spaces_manager';
@ -31,7 +31,13 @@ function getSpacesManager(spaces: Space[] = []) {
test('it renders without crashing', () => {
const spacesManager = getSpacesManager();
const component = shallow(<SpaceSelector spaces={[]} spacesManager={spacesManager as any} />);
const component = shallowWithIntl(
<SpaceSelector.WrappedComponent
spaces={[]}
spacesManager={spacesManager as any}
intl={null as any}
/>
);
expect(component).toMatchSnapshot();
});
@ -46,7 +52,13 @@ test('it uses the spaces on props, when provided', () => {
},
];
const component = render(<SpaceSelector spaces={spaces} spacesManager={spacesManager as any} />);
const component = renderWithIntl(
<SpaceSelector.WrappedComponent
spaces={spaces}
spacesManager={spacesManager as any}
intl={null as any}
/>
);
return Promise.resolve().then(() => {
expect(component.find('.spaceCard')).toHaveLength(1);
@ -65,7 +77,9 @@ test('it queries for spaces when not provided on props', () => {
const spacesManager = getSpacesManager(spaces);
shallow(<SpaceSelector spacesManager={spacesManager as any} />);
shallowWithIntl(
<SpaceSelector.WrappedComponent spacesManager={spacesManager as any} intl={null as any} />
);
return Promise.resolve().then(() => {
expect(spacesManager.getSpaces).toHaveBeenCalledTimes(1);

View file

@ -17,6 +17,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { SpacesManager } from 'plugins/spaces/lib';
import React, { Component, Fragment } from 'react';
import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants';
@ -26,6 +27,7 @@ import { SpaceCards } from '../components/space_cards';
interface Props {
spaces?: Space[];
spacesManager: SpacesManager;
intl: InjectedIntl;
}
interface State {
@ -34,7 +36,7 @@ interface State {
spaces: Space[];
}
export class SpaceSelector extends Component<Props, State> {
class SpaceSelectorUI extends Component<Props, State> {
constructor(props: Props) {
super(props);
@ -89,11 +91,22 @@ export class SpaceSelector extends Component<Props, State> {
<span className="spcSpaceSelector__logo">
<EuiIcon size="xxl" type={`logoKibana`} />
</span>
<EuiTitle size="l">
<h1>Select your space</h1>
<h1>
<FormattedMessage
id="xpack.spaces.spaceSelector.selectSpacesTitle"
defaultMessage="Select your space"
/>
</h1>
</EuiTitle>
<EuiText size="s" color="subdued">
<p>You can change your space at anytime</p>
<p>
<FormattedMessage
id="xpack.spaces.spaceSelector.changeSpaceAnytimeAvailabilityText"
defaultMessage="You can change your space at anytime"
/>
</p>
</EuiText>
</EuiPageHeader>
<EuiPageContent className="spcSpaceSelector__pageContent">
@ -118,7 +131,10 @@ export class SpaceSelector extends Component<Props, State> {
// @ts-ignore
textAlign="center"
>
No spaces match search criteria
<FormattedMessage
id="xpack.spaces.spaceSelector.noSpacesMatchSearchCriteriaDescription"
defaultMessage="No spaces match search criteria"
/>
</EuiText>
</Fragment>
)}
@ -129,6 +145,7 @@ export class SpaceSelector extends Component<Props, State> {
}
public getSearchField = () => {
const { intl } = this.props;
if (!this.props.spaces || this.props.spaces.length < SPACE_SEARCH_COUNT_THRESHOLD) {
return null;
}
@ -136,7 +153,10 @@ export class SpaceSelector extends Component<Props, State> {
<EuiFlexItem className="spcSpaceSelector__searchHolder">
<EuiFieldSearch
className="spcSpaceSelector__searchField"
placeholder="Find a space"
placeholder={intl.formatMessage({
id: 'xpack.spaces.spaceSelector.findSpacePlaceholder',
defaultMessage: 'Find a space',
})}
incremental={true}
// @ts-ignore
onSearch={this.onSearch}
@ -155,3 +175,5 @@ export class SpaceSelector extends Component<Props, State> {
this.props.spacesManager.changeSelectedSpace(space);
};
}
export const SpaceSelector = injectI18n(SpaceSelectorUI);

View file

@ -22,6 +22,9 @@
],
"plugins/spaces/*": [
"x-pack/plugins/spaces/public/*"
],
"test_utils/*": [
"x-pack/test_utils/*"
]
},
"types": [