[i18n] Translate ML - settings - calendar management (#27839)

* Translate settings -> calendar management

* Update snapshots

* Update test for calendar form

* Minor fix for id name
This commit is contained in:
Nox911 2019-01-03 11:23:35 +03:00 committed by GitHub
parent 63999aa7c7
commit 6a455833da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 524 additions and 189 deletions

View file

@ -11,7 +11,7 @@ exports[`NewCalendar Renders new calendar form 1`] = `
panelPaddingSize="l" panelPaddingSize="l"
verticalPosition="center" verticalPosition="center"
> >
<CalendarForm <InjectIntl(CalendarForm)
calendarId="" calendarId=""
canCreateCalendar={true} canCreateCalendar={true}
canDeleteCalendar={true} canDeleteCalendar={true}

View file

@ -1,5 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CalendarForm CalendarId shown as title when editing 1`] = `
<EuiTitle
size="m"
textTransform="none"
>
<h1
className="euiTitle euiTitle--medium"
>
<FormattedMessage
defaultMessage="Calendar {calendarId}"
id="xpack.ml.calendarsEdit.calendarForm.calendarTitle"
values={
Object {
"calendarId": "test-calendar",
}
}
>
Calendar test-calendar
</FormattedMessage>
</h1>
</EuiTitle>
`;
exports[`CalendarForm Renders calendar form 1`] = ` exports[`CalendarForm Renders calendar form 1`] = `
<EuiForm> <EuiForm>
<React.Fragment> <React.Fragment>
@ -7,14 +30,19 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []} describedByIds={Array []}
error={ error={
Array [ Array [
"Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores; "Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores; must start and end with an alphanumeric character",
must start and end with an alphanumeric character",
] ]
} }
fullWidth={false} fullWidth={false}
hasEmptyLabelSpace={false} hasEmptyLabelSpace={false}
isInvalid={true} isInvalid={true}
label="Calendar ID" label={
<FormattedMessage
defaultMessage="Calendar ID"
id="xpack.ml.calendarsEdit.calendarForm.calendarIdLabel"
values={Object {}}
/>
}
> >
<EuiFieldText <EuiFieldText
compressed={false} compressed={false}
@ -30,7 +58,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []} describedByIds={Array []}
fullWidth={false} fullWidth={false}
hasEmptyLabelSpace={false} hasEmptyLabelSpace={false}
label="Description" label={
<FormattedMessage
defaultMessage="Description"
id="xpack.ml.calendarsEdit.calendarForm.descriptionLabel"
values={Object {}}
/>
}
> >
<EuiFieldText <EuiFieldText
compressed={false} compressed={false}
@ -47,7 +81,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []} describedByIds={Array []}
fullWidth={false} fullWidth={false}
hasEmptyLabelSpace={false} hasEmptyLabelSpace={false}
label="Jobs" label={
<FormattedMessage
defaultMessage="Jobs"
id="xpack.ml.calendarsEdit.calendarForm.jobsLabel"
values={Object {}}
/>
}
> >
<EuiComboBox <EuiComboBox
compressed={false} compressed={false}
@ -64,7 +104,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []} describedByIds={Array []}
fullWidth={false} fullWidth={false}
hasEmptyLabelSpace={false} hasEmptyLabelSpace={false}
label="Groups" label={
<FormattedMessage
defaultMessage="Groups"
id="xpack.ml.calendarsEdit.calendarForm.groupsLabel"
values={Object {}}
/>
}
> >
<EuiComboBox <EuiComboBox
compressed={false} compressed={false}
@ -85,9 +131,15 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []} describedByIds={Array []}
fullWidth={true} fullWidth={true}
hasEmptyLabelSpace={false} hasEmptyLabelSpace={false}
label="Events" label={
<FormattedMessage
defaultMessage="Events"
id="xpack.ml.calendarsEdit.calendarForm.eventsLabel"
values={Object {}}
/>
}
> >
<EventsTable <InjectIntl(EventsTable)
canCreateCalendar={true} canCreateCalendar={true}
canDeleteCalendar={true} canDeleteCalendar={true}
eventsList={Array []} eventsList={Array []}
@ -122,7 +174,11 @@ exports[`CalendarForm Renders calendar form 1`] = `
onClick={[MockFunction]} onClick={[MockFunction]}
type="button" type="button"
> >
Save <FormattedMessage
defaultMessage="Save"
id="xpack.ml.calendarsEdit.calendarForm.saveButtonLabel"
values={Object {}}
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem <EuiFlexItem
@ -137,7 +193,11 @@ exports[`CalendarForm Renders calendar form 1`] = `
isDisabled={false} isDisabled={false}
type="button" type="button"
> >
Cancel <FormattedMessage
defaultMessage="Cancel"
id="xpack.ml.calendarsEdit.calendarForm.cancelButtonLabel"
values={Object {}}
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>

View file

@ -25,6 +25,8 @@ import {
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { EventsTable } from '../events_table/'; import { EventsTable } from '../events_table/';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
function EditHeader({ function EditHeader({
calendarId, calendarId,
@ -33,7 +35,13 @@ function EditHeader({
return ( return (
<Fragment> <Fragment>
<EuiTitle> <EuiTitle>
<h1>Calendar {calendarId}</h1> <h1>
<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.calendarTitle"
defaultMessage="Calendar {calendarId}"
values={{ calendarId }}
/>
</h1>
</EuiTitle> </EuiTitle>
<EuiText> <EuiText>
<p> <p>
@ -45,7 +53,7 @@ function EditHeader({
); );
} }
export function CalendarForm({ export const CalendarForm = injectI18n(function CalendarForm({
calendarId, calendarId,
canCreateCalendar, canCreateCalendar,
canDeleteCalendar, canDeleteCalendar,
@ -67,10 +75,14 @@ export function CalendarForm({
saving, saving,
selectedGroupOptions, selectedGroupOptions,
selectedJobOptions, selectedJobOptions,
showNewEventModal showNewEventModal,
intl
}) { }) {
const msg = `Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores; const msg = intl.formatMessage({
must start and end with an alphanumeric character`; id: 'xpack.ml.calendarsEdit.calendarForm.allowedCharactersDescription',
defaultMessage: 'Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores; ' +
'must start and end with an alphanumeric character'
});
const helpText = (isNewCalendarIdValid === true && !isEdit) ? msg : undefined; const helpText = (isNewCalendarIdValid === true && !isEdit) ? msg : undefined;
const error = (isNewCalendarIdValid === false && !isEdit) ? [msg] : undefined; const error = (isNewCalendarIdValid === false && !isEdit) ? [msg] : undefined;
const saveButtonDisabled = (canCreateCalendar === false || saving || !isNewCalendarIdValid || calendarId === ''); const saveButtonDisabled = (canCreateCalendar === false || saving || !isNewCalendarIdValid || calendarId === '');
@ -80,7 +92,10 @@ export function CalendarForm({
{!isEdit && {!isEdit &&
<Fragment> <Fragment>
<EuiFormRow <EuiFormRow
label="Calendar ID" label={<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.calendarIdLabel"
defaultMessage="Calendar ID"
/>}
helpText={helpText} helpText={helpText}
error={error} error={error}
isInvalid={!isNewCalendarIdValid} isInvalid={!isNewCalendarIdValid}
@ -94,7 +109,10 @@ export function CalendarForm({
</EuiFormRow> </EuiFormRow>
<EuiFormRow <EuiFormRow
label="Description" label={<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.descriptionLabel"
defaultMessage="Description"
/>}
> >
<EuiFieldText <EuiFieldText
name="description" name="description"
@ -111,7 +129,10 @@ export function CalendarForm({
description={description} description={description}
/>} />}
<EuiFormRow <EuiFormRow
label="Jobs" label={<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.jobsLabel"
defaultMessage="Jobs"
/>}
> >
<EuiComboBox <EuiComboBox
options={jobIds} options={jobIds}
@ -122,7 +143,10 @@ export function CalendarForm({
</EuiFormRow> </EuiFormRow>
<EuiFormRow <EuiFormRow
label="Groups" label={<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.groupsLabel"
defaultMessage="Groups"
/>}
> >
<EuiComboBox <EuiComboBox
onCreateOption={onCreateGroupOption} onCreateOption={onCreateGroupOption}
@ -136,7 +160,10 @@ export function CalendarForm({
<EuiSpacer size="xl" /> <EuiSpacer size="xl" />
<EuiFormRow <EuiFormRow
label="Events" label={<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.eventsLabel"
defaultMessage="Events"
/>}
fullWidth fullWidth
> >
<EventsTable <EventsTable
@ -158,7 +185,13 @@ export function CalendarForm({
onClick={isEdit ? onEdit : onCreate} onClick={isEdit ? onEdit : onCreate}
isDisabled={saveButtonDisabled} isDisabled={saveButtonDisabled}
> >
{saving ? 'Saving...' : 'Save'} {saving ? (<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.savingButtonLabel"
defaultMessage="Saving…"
/>) : (<FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.saveButtonLabel"
defaultMessage="Save"
/>)}
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
@ -166,15 +199,18 @@ export function CalendarForm({
isDisabled={saving} isDisabled={saving}
href={`${chrome.getBasePath()}/app/ml#/settings/calendars_list`} href={`${chrome.getBasePath()}/app/ml#/settings/calendars_list`}
> >
Cancel <FormattedMessage
id="xpack.ml.calendarsEdit.calendarForm.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButton> </EuiButton>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
</EuiForm> </EuiForm>
); );
} });
CalendarForm.propTypes = { CalendarForm.WrappedComponent.propTypes = {
calendarId: PropTypes.string.isRequired, calendarId: PropTypes.string.isRequired,
canCreateCalendar: PropTypes.bool.isRequired, canCreateCalendar: PropTypes.bool.isRequired,
canDeleteCalendar: PropTypes.bool.isRequired, canDeleteCalendar: PropTypes.bool.isRequired,

View file

@ -11,7 +11,7 @@ jest.mock('ui/chrome', () => ({
})); }));
import { shallow, mount } from 'enzyme'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { CalendarForm } from './calendar_form'; import { CalendarForm } from './calendar_form';
@ -43,8 +43,8 @@ const testProps = {
describe('CalendarForm', () => { describe('CalendarForm', () => {
test('Renders calendar form', () => { test('Renders calendar form', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<CalendarForm {...testProps}/> <CalendarForm.WrappedComponent {...testProps}/>
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@ -57,16 +57,12 @@ describe('CalendarForm', () => {
calendarId: 'test-calendar', calendarId: 'test-calendar',
description: 'test description', description: 'test description',
}; };
const wrapper = mount( const wrapper = mountWithIntl(
<CalendarForm {...editProps} /> <CalendarForm.WrappedComponent {...editProps} />
); );
const calendarId = wrapper.find('EuiTitle'); const calendarId = wrapper.find('EuiTitle');
expect( expect(calendarId).toMatchSnapshot();
calendarId.containsMatchingElement(
<h1>Calendar test-calendar</h1>
)
).toBeTruthy();
}); });
}); });

View file

@ -20,6 +20,8 @@ import { getCreateCalendarBreadcrumbs, getEditCalendarBreadcrumbs } from '../../
import uiRoutes from 'ui/routes'; import uiRoutes from 'ui/routes';
import { I18nProvider } from '@kbn/i18n/react';
const template = ` const template = `
<ml-nav-menu name="settings" /> <ml-nav-menu name="settings" />
<div class="mlCalendarManagement"> <div class="mlCalendarManagement">
@ -64,7 +66,9 @@ module.directive('mlNewCalendar', function ($route) {
}; };
ReactDOM.render( ReactDOM.render(
React.createElement(NewCalendar, props), <I18nProvider>
{React.createElement(NewCalendar, props)}
</I18nProvider>,
element[0] element[0]
); );
} }

View file

@ -140,7 +140,11 @@ exports[`EventsTable Renders events table with search bar 1`] = `
size="s" size="s"
type="button" type="button"
> >
New event <FormattedMessage
defaultMessage="New event"
id="xpack.ml.calendarsEdit.eventsTable.newEventButtonLabel"
values={Object {}}
/>
</EuiButton>, </EuiButton>,
<EuiButton <EuiButton
color="primary" color="primary"
@ -153,7 +157,11 @@ exports[`EventsTable Renders events table with search bar 1`] = `
size="s" size="s"
type="button" type="button"
> >
Import events <FormattedMessage
defaultMessage="Import events"
id="xpack.ml.calendarsEdit.eventsTable.importEventsButtonLabel"
values={Object {}}
/>
</EuiButton>, </EuiButton>,
], ],
} }

View file

@ -17,6 +17,8 @@ import {
EuiSpacer, EuiSpacer,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
function DeleteButton({ onClick, canDeleteCalendar }) { function DeleteButton({ onClick, canDeleteCalendar }) {
@ -28,20 +30,24 @@ function DeleteButton({ onClick, canDeleteCalendar }) {
onClick={onClick} onClick={onClick}
isDisabled={canDeleteCalendar === false} isDisabled={canDeleteCalendar === false}
> >
Delete <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.deleteButtonLabel"
defaultMessage="Delete"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</Fragment> </Fragment>
); );
} }
export function EventsTable({ export const EventsTable = injectI18n(function EventsTable({
canCreateCalendar, canCreateCalendar,
canDeleteCalendar, canDeleteCalendar,
eventsList, eventsList,
onDeleteClick, onDeleteClick,
showSearchBar, showSearchBar,
showImportModal, showImportModal,
showNewEventModal showNewEventModal,
intl
}) { }) {
const sorting = { const sorting = {
sort: { sort: {
@ -58,13 +64,19 @@ export function EventsTable({
const columns = [ const columns = [
{ {
field: 'description', field: 'description',
name: 'Description', name: intl.formatMessage({
id: 'xpack.ml.calendarsEdit.eventsTable.descriptionColumnName',
defaultMessage: 'Description'
}),
sortable: true, sortable: true,
truncateText: true truncateText: true
}, },
{ {
field: 'start_time', field: 'start_time',
name: 'Start', name: intl.formatMessage({
id: 'xpack.ml.calendarsEdit.eventsTable.startColumnName',
defaultMessage: 'Start'
}),
sortable: true, sortable: true,
render: (timeMs) => { render: (timeMs) => {
const time = moment(timeMs); const time = moment(timeMs);
@ -73,7 +85,10 @@ export function EventsTable({
}, },
{ {
field: 'end_time', field: 'end_time',
name: 'End', name: intl.formatMessage({
id: 'xpack.ml.calendarsEdit.eventsTable.endColumnName',
defaultMessage: 'End'
}),
sortable: true, sortable: true,
render: (timeMs) => { render: (timeMs) => {
const time = moment(timeMs); const time = moment(timeMs);
@ -103,7 +118,10 @@ export function EventsTable({
iconType="plusInCircle" iconType="plusInCircle"
onClick={showNewEventModal} onClick={showNewEventModal}
> >
New event <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.newEventButtonLabel"
defaultMessage="New event"
/>
</EuiButton>), </EuiButton>),
( (
<EuiButton <EuiButton
@ -114,7 +132,10 @@ export function EventsTable({
iconType="importAction" iconType="importAction"
onClick={showImportModal} onClick={showImportModal}
> >
Import events <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importEventsButtonLabel"
defaultMessage="Import events"
/>
</EuiButton> </EuiButton>
)], )],
box: { box: {
@ -136,9 +157,9 @@ export function EventsTable({
/> />
</Fragment> </Fragment>
); );
} });
EventsTable.propTypes = { EventsTable.WrappedComponent.propTypes = {
canCreateCalendar: PropTypes.bool, canCreateCalendar: PropTypes.bool,
canDeleteCalendar: PropTypes.bool, canDeleteCalendar: PropTypes.bool,
eventsList: PropTypes.array.isRequired, eventsList: PropTypes.array.isRequired,
@ -148,7 +169,7 @@ EventsTable.propTypes = {
showSearchBar: PropTypes.bool, showSearchBar: PropTypes.bool,
}; };
EventsTable.defaultProps = { EventsTable.WrappedComponent.defaultProps = {
showSearchBar: false, showSearchBar: false,
canCreateCalendar: true, canCreateCalendar: true,
canDeleteCalendar: true canDeleteCalendar: true

View file

@ -11,7 +11,7 @@ jest.mock('ui/chrome', () => ({
})); }));
import { shallow } from 'enzyme'; import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { EventsTable } from './events_table'; import { EventsTable } from './events_table';
@ -33,8 +33,8 @@ const testProps = {
describe('EventsTable', () => { describe('EventsTable', () => {
test('Renders events table with no search bar', () => { test('Renders events table with no search bar', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<EventsTable {...testProps}/> <EventsTable.WrappedComponent {...testProps}/>
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@ -46,8 +46,8 @@ describe('EventsTable', () => {
showSearchBar: true, showSearchBar: true,
}; };
const wrapper = shallow( const wrapper = shallowWithIntl(
<EventsTable {...showSearchBarProps} /> <EventsTable.WrappedComponent {...showSearchBarProps} />
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View file

@ -21,7 +21,11 @@ exports[`ImportModal Renders import modal 1`] = `
grow={false} grow={false}
> >
<EuiModalHeaderTitle> <EuiModalHeaderTitle>
Import events <FormattedMessage
defaultMessage="Import events"
id="xpack.ml.calendarsEdit.eventsTable.importEventsTitle"
values={Object {}}
/>
</EuiModalHeaderTitle> </EuiModalHeaderTitle>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem <EuiFlexItem
@ -29,7 +33,11 @@ exports[`ImportModal Renders import modal 1`] = `
grow={false} grow={false}
> >
<p> <p>
Import events from an ICS file. <FormattedMessage
defaultMessage="Import events from an ICS file."
id="xpack.ml.calendarsEdit.eventsTable.importEventsDescription"
values={Object {}}
/>
</p> </p>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
@ -66,7 +74,11 @@ exports[`ImportModal Renders import modal 1`] = `
onClick={[Function]} onClick={[Function]}
type="button" type="button"
> >
Import <FormattedMessage
defaultMessage="Import"
id="xpack.ml.calendarsEdit.eventsTable.importButtonLabel"
values={Object {}}
/>
</EuiButton> </EuiButton>
<EuiButtonEmpty <EuiButtonEmpty
color="primary" color="primary"
@ -74,7 +86,11 @@ exports[`ImportModal Renders import modal 1`] = `
onClick={[MockFunction]} onClick={[MockFunction]}
type="button" type="button"
> >
Cancel <FormattedMessage
defaultMessage="Cancel"
id="xpack.ml.calendarsEdit.eventsTable.cancelButtonLabel"
values={Object {}}
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiModalFooter> </EuiModalFooter>
</EuiModal> </EuiModal>

View file

@ -27,9 +27,16 @@ import {
import { ImportedEvents } from '../imported_events'; import { ImportedEvents } from '../imported_events';
import { readFile, parseICSFile, filterEvents } from './utils'; import { readFile, parseICSFile, filterEvents } from './utils';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
const MAX_FILE_SIZE_MB = 100; const MAX_FILE_SIZE_MB = 100;
export class ImportModal extends Component { export const ImportModal = injectI18n(class ImportModal extends Component {
static propTypes = {
addImportedEvents: PropTypes.func.isRequired,
closeImportModal: PropTypes.func.isRequired,
}
constructor(props) { constructor(props) {
super(props); super(props);
@ -45,7 +52,10 @@ export class ImportModal extends Component {
handleImport = async (loadedFile) => { handleImport = async (loadedFile) => {
const incomingFile = loadedFile[0]; const incomingFile = loadedFile[0];
const errorMessage = 'Could not parse ICS file.'; const errorMessage = this.props.intl.formatMessage({
id: 'xpack.ml.calendarsEdit.importModal.couldNotParseICSFileErrorMessage',
defaultMessage: 'Could not parse ICS file.'
});
let events = []; let events = [];
if (incomingFile && incomingFile.size <= (MAX_FILE_SIZE_MB * 1000000)) { if (incomingFile && incomingFile.size <= (MAX_FILE_SIZE_MB * 1000000)) {
@ -107,7 +117,7 @@ export class ImportModal extends Component {
); );
render() { render() {
const { closeImportModal } = this.props; const { closeImportModal, intl } = this.props;
const { const {
fileLoading, fileLoading,
fileLoaded, fileLoaded,
@ -143,11 +153,19 @@ export class ImportModal extends Component {
> >
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiModalHeaderTitle > <EuiModalHeaderTitle >
Import events <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importEventsTitle"
defaultMessage="Import events"
/>
</EuiModalHeaderTitle> </EuiModalHeaderTitle>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<p>Import events from an ICS file.</p> <p>
<FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importEventsDescription"
defaultMessage="Import events from an ICS file."
/>
</p>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
</EuiModalHeader> </EuiModalHeader>
@ -157,7 +175,10 @@ export class ImportModal extends Component {
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiFilePicker <EuiFilePicker
compressed compressed
initialPromptText="Select or drag and drop a file" initialPromptText={intl.formatMessage({
id: 'xpack.ml.calendarsEdit.importModal.selectOrDragAndDropFilePromptText',
defaultMessage: 'Select or drag and drop a file'
})}
onChange={this.handleImport} onChange={this.handleImport}
disabled={fileLoading} disabled={fileLoading}
/> />
@ -182,21 +203,22 @@ export class ImportModal extends Component {
fill fill
disabled={fileLoaded === false || errorMessage !== null} disabled={fileLoaded === false || errorMessage !== null}
> >
Import <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importButtonLabel"
defaultMessage="Import"
/>
</EuiButton> </EuiButton>
<EuiButtonEmpty <EuiButtonEmpty
onClick={closeImportModal} onClick={closeImportModal}
> >
Cancel <FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiModalFooter> </EuiModalFooter>
</EuiModal> </EuiModal>
</Fragment> </Fragment>
); );
} }
} });
ImportModal.propTypes = {
addImportedEvents: PropTypes.func.isRequired,
closeImportModal: PropTypes.func.isRequired,
};

View file

@ -6,7 +6,7 @@
import { shallow, mount } from 'enzyme'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { ImportModal } from './import_modal'; import { ImportModal } from './import_modal';
@ -34,16 +34,16 @@ const events = [{
describe('ImportModal', () => { describe('ImportModal', () => {
test('Renders import modal', () => { test('Renders import modal', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<ImportModal {...testProps}/> <ImportModal.WrappedComponent {...testProps}/>
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('Deletes selected event from event table', () => { test('Deletes selected event from event table', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<ImportModal {...testProps} /> <ImportModal.WrappedComponent {...testProps} />
); );
const testState = { const testState = {

View file

@ -14,8 +14,15 @@ exports[`ImportedEvents Renders imported events 1`] = `
size="m" size="m"
> >
<h4> <h4>
Events to import: <FormattedMessage
1 defaultMessage="Events to import: {eventsCount}"
id="xpack.ml.calendarsEdit.importedEvents.eventsToImportTitle"
values={
Object {
"eventsCount": 1,
}
}
/>
</h4> </h4>
</EuiText> </EuiText>
</EuiFlexItem> </EuiFlexItem>
@ -23,9 +30,7 @@ exports[`ImportedEvents Renders imported events 1`] = `
component="div" component="div"
grow={false} grow={false}
> >
<EventsTable <InjectIntl(EventsTable)
canCreateCalendar={true}
canDeleteCalendar={true}
eventsList={ eventsList={
Array [ Array [
Object { Object {
@ -38,7 +43,6 @@ exports[`ImportedEvents Renders imported events 1`] = `
] ]
} }
onDeleteClick={[MockFunction]} onDeleteClick={[MockFunction]}
showSearchBar={false}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiSpacer <EuiSpacer
@ -54,7 +58,13 @@ exports[`ImportedEvents Renders imported events 1`] = `
disabled={false} disabled={false}
id="ml-include-past-events" id="ml-include-past-events"
indeterminate={false} indeterminate={false}
label="Include past events" label={
<FormattedMessage
defaultMessage="Include past events"
id="xpack.ml.calendarsEdit.importedEvents.includePastEventsLabel"
values={Object {}}
/>
}
onChange={[MockFunction]} onChange={[MockFunction]}
/> />
</EuiFlexItem> </EuiFlexItem>

View file

@ -13,6 +13,7 @@ import {
EuiSpacer EuiSpacer
} from '@elastic/eui'; } from '@elastic/eui';
import { EventsTable } from '../events_table/'; import { EventsTable } from '../events_table/';
import { FormattedMessage } from '@kbn/i18n/react';
export function ImportedEvents({ export function ImportedEvents({
@ -27,10 +28,21 @@ export function ImportedEvents({
<EuiSpacer size="s"/> <EuiSpacer size="s"/>
<EuiFlexItem> <EuiFlexItem>
<EuiText> <EuiText>
<h4>Events to import: {events.length}</h4> <h4>
<FormattedMessage
id="xpack.ml.calendarsEdit.importedEvents.eventsToImportTitle"
defaultMessage="Events to import: {eventsCount}"
values={{ eventsCount: events.length }}
/>
</h4>
{showRecurringWarning && ( {showRecurringWarning && (
<EuiText color="danger"> <EuiText color="danger">
<p>Recurring events not supported. Only the first event will be imported.</p> <p>
<FormattedMessage
id="xpack.ml.calendarsEdit.importedEvents.recurringEventsNotSupportedDescription"
defaultMessage="Recurring events not supported. Only the first event will be imported."
/>
</p>
</EuiText>) </EuiText>)
} }
</EuiText> </EuiText>
@ -45,7 +57,10 @@ export function ImportedEvents({
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiCheckbox <EuiCheckbox
id="ml-include-past-events" id="ml-include-past-events"
label="Include past events" label={<FormattedMessage
id="xpack.ml.calendarsEdit.importedEvents.includePastEventsLabel"
defaultMessage="Include past events"
/>}
checked={includePastEvents} checked={includePastEvents}
onChange={onCheckboxToggle} onChange={onCheckboxToggle}
/> />

View file

@ -11,7 +11,7 @@ jest.mock('ui/chrome', () => ({
})); }));
import { shallow } from 'enzyme'; import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { ImportedEvents } from './imported_events'; import { ImportedEvents } from './imported_events';
@ -33,7 +33,7 @@ const testProps = {
describe('ImportedEvents', () => { describe('ImportedEvents', () => {
test('Renders imported events', () => { test('Renders imported events', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<ImportedEvents {...testProps} /> <ImportedEvents {...testProps} />
); );

View file

@ -25,7 +25,15 @@ import { ImportModal } from './import_modal';
import { ml } from '../../../services/ml_api_service'; import { ml } from '../../../services/ml_api_service';
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
export class NewCalendar extends Component { import { injectI18n } from '@kbn/i18n/react';
export const NewCalendar = injectI18n(class NewCalendar extends Component {
static propTypes = {
calendarId: PropTypes.string,
canCreateCalendar: PropTypes.bool.isRequired,
canDeleteCalendar: PropTypes.bool.isRequired,
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -99,7 +107,12 @@ export class NewCalendar extends Component {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
this.setState({ loading: false }); this.setState({ loading: false });
toastNotifications.addDanger('An error occurred loading calendar form data. Try refreshing the page.'); toastNotifications.addDanger(
this.props.intl.formatMessage({
id: 'xpack.ml.calendarsEdit.errorWithLoadingCalendarFromDataErrorMessage',
defaultMessage: 'An error occurred loading calendar form data. Try refreshing the page.'
})
);
} }
} }
@ -117,9 +130,18 @@ export class NewCalendar extends Component {
onCreate = async () => { onCreate = async () => {
const { formCalendarId } = this.state; const { formCalendarId } = this.state;
const { intl } = this.props;
if (this.isDuplicateId()) { if (this.isDuplicateId()) {
toastNotifications.addDanger(`Cannot create calendar with id [${formCalendarId}] as it already exists.`); toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.ml.calendarsEdit.canNotCreateCalendarWithExistingIdErrorMessag',
defaultMessage: 'Cannot create calendar with id [{formCalendarId}] as it already exists.'
},
{ formCalendarId }
)
);
} else { } else {
const calendar = this.setUpCalendarForApi(); const calendar = this.setUpCalendarForApi();
this.setState({ saving: true }); this.setState({ saving: true });
@ -130,7 +152,15 @@ export class NewCalendar extends Component {
} catch (error) { } catch (error) {
console.log('Error saving calendar', error); console.log('Error saving calendar', error);
this.setState({ saving: false }); this.setState({ saving: false });
toastNotifications.addDanger(`An error occurred creating calendar ${calendar.calendarId}`); toastNotifications.addDanger(
intl.formatMessage(
{
id: 'xpack.ml.calendarsEdit.errorWithCreatingCalendarErrorMessage',
defaultMessage: 'An error occurred creating calendar {calendarId}'
},
{ calendarId: calendar.calendarId }
)
);
} }
} }
} }
@ -145,7 +175,15 @@ export class NewCalendar extends Component {
} catch (error) { } catch (error) {
console.log('Error saving calendar', error); console.log('Error saving calendar', error);
this.setState({ saving: false }); this.setState({ saving: false });
toastNotifications.addDanger(`An error occurred saving calendar ${calendar.calendarId}. Try refreshing the page.`); toastNotifications.addDanger(
this.props.intl.formatMessage(
{
id: 'xpack.ml.calendarsEdit.errorWithUpdatingCalendarErrorMessage',
defaultMessage: 'An error occurred saving calendar {calendarId}. Try refreshing the page.'
},
{ calendarId: calendar.calendarId }
)
);
} }
} }
@ -330,10 +368,4 @@ export class NewCalendar extends Component {
</EuiPage> </EuiPage>
); );
} }
} });
NewCalendar.propTypes = {
calendarId: PropTypes.string,
canCreateCalendar: PropTypes.bool.isRequired,
canDeleteCalendar: PropTypes.bool.isRequired,
};

View file

@ -46,7 +46,7 @@ jest.mock('./utils', () => ({
})), })),
})); }));
import { shallow, mount } from 'enzyme'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { NewCalendar } from './new_calendar'; import { NewCalendar } from './new_calendar';
@ -84,16 +84,16 @@ const props = {
describe('NewCalendar', () => { describe('NewCalendar', () => {
test('Renders new calendar form', () => { test('Renders new calendar form', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<NewCalendar {...props}/> <NewCalendar.WrappedComponent {...props}/>
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('Import modal shown on Import Events button click', () => { test('Import modal shown on Import Events button click', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<NewCalendar {...props}/> <NewCalendar.WrappedComponent {...props}/>
); );
const importButton = wrapper.find('[data-testid="ml_import_events"]'); const importButton = wrapper.find('[data-testid="ml_import_events"]');
@ -104,8 +104,8 @@ describe('NewCalendar', () => {
}); });
test('New event modal shown on New event button click', () => { test('New event modal shown on New event button click', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<NewCalendar {...props}/> <NewCalendar.WrappedComponent {...props}/>
); );
const importButton = wrapper.find('[data-testid="ml_new_event"]'); const importButton = wrapper.find('[data-testid="ml_new_event"]');
@ -116,8 +116,8 @@ describe('NewCalendar', () => {
}); });
test('isDuplicateId returns true if form calendar id already exists in calendars', () => { test('isDuplicateId returns true if form calendar id already exists in calendars', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<NewCalendar {...props}/> <NewCalendar.WrappedComponent {...props}/>
); );
const instance = wrapper.instance(); const instance = wrapper.instance();
@ -135,8 +135,8 @@ describe('NewCalendar', () => {
canCreateCalendar: false, canCreateCalendar: false,
}; };
const wrapper = mount( const wrapper = mountWithIntl(
<NewCalendar {...noCreateProps} /> <NewCalendar.WrappedComponent {...noCreateProps} />
); );
const buttons = wrapper.find('[data-testid="ml_save_calendar_button"]'); const buttons = wrapper.find('[data-testid="ml_save_calendar_button"]');

View file

@ -31,9 +31,16 @@ import moment from 'moment';
import { TIME_FORMAT } from '../events_table/'; import { TIME_FORMAT } from '../events_table/';
import { generateTempId } from '../utils'; import { generateTempId } from '../utils';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
const VALID_DATE_STRING_LENGTH = 19; const VALID_DATE_STRING_LENGTH = 19;
export class NewEventModal extends Component { export const NewEventModal = injectI18n(class NewEventModal extends Component {
static propTypes = {
closeModal: PropTypes.func.isRequired,
addEvent: PropTypes.func.isRequired,
};
constructor(props) { constructor(props) {
super(props); super(props);
@ -158,11 +165,19 @@ export class NewEventModal extends Component {
endDateString, endDateString,
} = this.state; } = this.state;
const { intl } = this.props;
const timeInputs = ( const timeInputs = (
<Fragment> <Fragment>
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem> <EuiFlexItem>
<EuiFormRow label="From:" helpText={TIME_FORMAT}> <EuiFormRow
label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.fromLabel"
defaultMessage="From:"
/>}
helpText={TIME_FORMAT}
>
<EuiFieldText <EuiFieldText
name="startTime" name="startTime"
onChange={this.handleTimeStartChange} onChange={this.handleTimeStartChange}
@ -172,7 +187,13 @@ export class NewEventModal extends Component {
</EuiFormRow> </EuiFormRow>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem> <EuiFlexItem>
<EuiFormRow label="To:" helpText={TIME_FORMAT}> <EuiFormRow
label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.toLabel"
defaultMessage="To:"
/>}
helpText={TIME_FORMAT}
>
<EuiFieldText <EuiFieldText
name="endTime" name="endTime"
onChange={this.handleTimeEndChange} onChange={this.handleTimeEndChange}
@ -203,7 +224,10 @@ export class NewEventModal extends Component {
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
isInvalid={startDate > endDate} isInvalid={startDate > endDate}
aria-label="Start date" aria-label={intl.formatMessage({
id: 'xpack.ml.calendarsEdit.newEventModal.startDateAriaLabel',
defaultMessage: 'Start date'
})}
timeFormat={TIME_FORMAT} timeFormat={TIME_FORMAT}
dateFormat={TIME_FORMAT} dateFormat={TIME_FORMAT}
/> />
@ -217,7 +241,10 @@ export class NewEventModal extends Component {
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
isInvalid={startDate > endDate} isInvalid={startDate > endDate}
aria-label="End date" aria-label={intl.formatMessage({
id: 'xpack.ml.calendarsEdit.newEventModal.endDateAriaLabel',
defaultMessage: 'End date'
})}
timeFormat={TIME_FORMAT} timeFormat={TIME_FORMAT}
dateFormat={TIME_FORMAT} dateFormat={TIME_FORMAT}
/> />
@ -241,14 +268,20 @@ export class NewEventModal extends Component {
> >
<EuiModalHeader> <EuiModalHeader>
<EuiModalHeaderTitle > <EuiModalHeaderTitle >
Create new event <FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.createNewEventTitle"
defaultMessage="Create new event"
/>
</EuiModalHeaderTitle> </EuiModalHeaderTitle>
</EuiModalHeader> </EuiModalHeader>
<EuiModalBody> <EuiModalBody>
<EuiForm> <EuiForm>
<EuiFormRow <EuiFormRow
label="Description" label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.descriptionLabel"
defaultMessage="Description"
/>}
fullWidth fullWidth
> >
<EuiFieldText <EuiFieldText
@ -269,21 +302,22 @@ export class NewEventModal extends Component {
fill fill
disabled={!description} disabled={!description}
> >
Add <FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.addButtonLabel"
defaultMessage="Add"
/>
</EuiButton> </EuiButton>
<EuiButtonEmpty <EuiButtonEmpty
onClick={closeModal} onClick={closeModal}
> >
Cancel <FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiModalFooter> </EuiModalFooter>
</EuiModal> </EuiModal>
</Fragment> </Fragment>
); );
} }
} });
NewEventModal.propTypes = {
closeModal: PropTypes.func.isRequired,
addEvent: PropTypes.func.isRequired,
};

View file

@ -6,7 +6,7 @@
import { shallow } from 'enzyme'; import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { NewEventModal } from './new_event_modal'; import { NewEventModal } from './new_event_modal';
import moment from 'moment'; import moment from 'moment';
@ -24,8 +24,8 @@ const stateTimestamps = {
describe('NewEventModal', () => { describe('NewEventModal', () => {
it('Add button disabled if description empty', () => { it('Add button disabled if description empty', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<NewEventModal {...testProps} /> <NewEventModal.WrappedComponent {...testProps} />
); );
const addButton = wrapper.find('EuiButton').first(); const addButton = wrapper.find('EuiButton').first();
@ -33,7 +33,7 @@ describe('NewEventModal', () => {
}); });
it('if endDate is less than startDate should set startDate one day before endDate', () => { it('if endDate is less than startDate should set startDate one day before endDate', () => {
const wrapper = shallow(<NewEventModal {...testProps} />); const wrapper = shallowWithIntl(<NewEventModal.WrappedComponent {...testProps} />);
const instance = wrapper.instance(); const instance = wrapper.instance();
instance.setState({ instance.setState({
startDate: moment(stateTimestamps.startDate), startDate: moment(stateTimestamps.startDate),
@ -53,7 +53,7 @@ describe('NewEventModal', () => {
}); });
it('if startDate is greater than endDate should set endDate one day after startDate', () => { it('if startDate is greater than endDate should set endDate one day after startDate', () => {
const wrapper = shallow(<NewEventModal {...testProps} />); const wrapper = shallowWithIntl(<NewEventModal.WrappedComponent {...testProps} />);
const instance = wrapper.instance(); const instance = wrapper.instance();
instance.setState({ instance.setState({
startDate: moment(stateTimestamps.startDate), startDate: moment(stateTimestamps.startDate),

View file

@ -8,6 +8,7 @@
import { ml } from '../../../services/ml_api_service'; import { ml } from '../../../services/ml_api_service';
import { isJobIdValid } from '../../../../common/util/job_utils'; import { isJobIdValid } from '../../../../common/util/job_utils';
import { i18n } from '@kbn/i18n';
function getJobIds() { function getJobIds() {
@ -17,7 +18,10 @@ function getJobIds() {
resolve(resp.map((job) => job.id)); resolve(resp.map((job) => job.id));
}) })
.catch((err) => { .catch((err) => {
const errorMessage = `Error fetching job summaries: ${err}`; const errorMessage = i18n.translate('xpack.ml.calendarsEdit.errorWithFetchingJobSummariesErrorMessage', {
defaultMessage: 'Error fetching job summaries: {err}',
values: { err }
});
console.log(errorMessage); console.log(errorMessage);
reject(errorMessage); reject(errorMessage);
}); });
@ -31,7 +35,10 @@ function getGroupIds() {
resolve(resp.map((group) => group.id)); resolve(resp.map((group) => group.id));
}) })
.catch((err) => { .catch((err) => {
const errorMessage = `Error loading groups: ${err}`; const errorMessage = i18n.translate('xpack.ml.calendarsEdit.errorWithLoadingGroupsErrorMessage', {
defaultMessage: 'Error loading groups: {err}',
values: { err }
});
console.log(errorMessage); console.log(errorMessage);
reject(errorMessage); reject(errorMessage);
}); });
@ -45,7 +52,10 @@ function getCalendars() {
resolve(resp); resolve(resp);
}) })
.catch((err) => { .catch((err) => {
const errorMessage = `Error loading calendars: ${err}`; const errorMessage = i18n.translate('xpack.ml.calendarsEdit.errorWithLoadingCalendarsErrorMessage', {
defaultMessage: 'Error loading calendars: {err}',
values: { err }
});
console.log(errorMessage); console.log(errorMessage);
reject(errorMessage); reject(errorMessage);
}); });

View file

@ -11,7 +11,7 @@ exports[`CalendarsList Renders calendar list with calendars 1`] = `
panelPaddingSize="l" panelPaddingSize="l"
verticalPosition="center" verticalPosition="center"
> >
<CalendarsListTable <InjectIntl(CalendarsListTable)
calendarsList={ calendarsList={
Array [ Array [
Object { Object {

View file

@ -24,8 +24,14 @@ import { ml } from '../../../services/ml_api_service';
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes'; import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { deleteCalendars } from './delete_calendars'; import { deleteCalendars } from './delete_calendars';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
export const CalendarsList = injectI18n(class CalendarsList extends Component {
static propTypes = {
canCreateCalendar: PropTypes.bool.isRequired,
canDeleteCalendar: PropTypes.bool.isRequired,
};
export class CalendarsList extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -50,7 +56,12 @@ export class CalendarsList extends Component {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
this.setState({ loading: false }); this.setState({ loading: false });
toastNotifications.addDanger('An error occurred loading the list of calendars.'); toastNotifications.addDanger(
this.props.intl.formatMessage({
id: 'xpack.ml.calendarsList.errorWithLoadingListOfCalendarsErrorMessage',
defaultMessage: 'An error occurred loading the list of calendars.'
})
);
} }
} }
@ -100,20 +111,32 @@ export class CalendarsList extends Component {
destroyModal = ( destroyModal = (
<EuiOverlayMask> <EuiOverlayMask>
<EuiConfirmModal <EuiConfirmModal
title="Delete calendar" title={<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarTitle"
defaultMessage="Delete calendar"
/>}
onCancel={this.closeDestroyModal} onCancel={this.closeDestroyModal}
onConfirm={this.deleteCalendars} onConfirm={this.deleteCalendars}
cancelButtonText="Cancel" cancelButtonText={<FormattedMessage
confirmButtonText="Delete" id="xpack.ml.calendarsList.deleteCalendarsModal.cancelButtonLabel"
defaultMessage="Cancel"
/>}
confirmButtonText={<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.deleteButtonLabel"
defaultMessage="Delete"
/>}
buttonColor="danger" buttonColor="danger"
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
> >
<p> <p>
{ <FormattedMessage
`Delete ${selectedForDeletion.length === 1 ? 'this' : 'these'} id="xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarsDescription"
calendar${selectedForDeletion.length === 1 ? '' : 's'}? defaultMessage="Delete {calendarsCount, plural, one {this calendar} other {these calendars}}? {calendarsList}"
${selectedForDeletion.map((c) => c.calendar_id).join(', ')}` values={{
} calendarsCount: selectedForDeletion.length,
calendarsList: (selectedForDeletion.map((c) => c.calendar_id).join(', '))
}}
/>
</p> </p>
</EuiConfirmModal> </EuiConfirmModal>
</EuiOverlayMask> </EuiOverlayMask>
@ -142,9 +165,4 @@ export class CalendarsList extends Component {
</EuiPage> </EuiPage>
); );
} }
} });
CalendarsListTable.propTypes = {
canCreateCalendar: PropTypes.bool.isRequired,
canDeleteCalendar: PropTypes.bool.isRequired,
};

View file

@ -30,7 +30,7 @@ jest.mock('../../../services/ml_api_service', () => ({
} }
})); }));
import { shallow, mount } from 'enzyme'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { ml } from '../../../services/ml_api_service'; import { ml } from '../../../services/ml_api_service';
@ -78,16 +78,16 @@ describe('CalendarsList', () => {
test('loads calendars on mount', () => { test('loads calendars on mount', () => {
ml.calendars = jest.fn(); ml.calendars = jest.fn();
shallow( shallowWithIntl(
<CalendarsList {...props}/> <CalendarsList.WrappedComponent {...props}/>
); );
expect(ml.calendars).toHaveBeenCalled(); expect(ml.calendars).toHaveBeenCalled();
}); });
test('Renders calendar list with calendars', () => { test('Renders calendar list with calendars', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<CalendarsList {...props}/> <CalendarsList.WrappedComponent {...props}/>
); );
wrapper.instance().setState(testingState); wrapper.instance().setState(testingState);
@ -96,8 +96,8 @@ describe('CalendarsList', () => {
}); });
test('Sets selected calendars list on checkbox change', () => { test('Sets selected calendars list on checkbox change', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<CalendarsList {...props}/> <CalendarsList.WrappedComponent {...props}/>
); );
const instance = wrapper.instance(); const instance = wrapper.instance();

View file

@ -6,6 +6,7 @@
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
import { ml } from '../../../services/ml_api_service'; import { ml } from '../../../services/ml_api_service';
import { i18n } from '@kbn/i18n';
export async function deleteCalendars(calendarsToDelete, callback) { export async function deleteCalendars(calendarsToDelete, callback) {
@ -15,9 +16,17 @@ export async function deleteCalendars(calendarsToDelete, callback) {
// Delete each of the specified calendars in turn, waiting for each response // Delete each of the specified calendars in turn, waiting for each response
// before deleting the next to minimize load on the cluster. // before deleting the next to minimize load on the cluster.
const messageId = `${(calendarsToDelete.length > 1) ? const messageId = (calendarsToDelete.length > 1)
`${calendarsToDelete.length} calendars` : calendarsToDelete[0].calendar_id}`; ? i18n.translate('xpack.ml.calendarsList.deleteCalendars.calendarsLabel', {
toastNotifications.add(`Deleting ${messageId}`); defaultMessage: '{calendarsToDeleteCount} calendars',
values: { calendarsToDeleteCount: calendarsToDelete.length }
}) : `${calendarsToDelete[0].calendar_id}`;
toastNotifications.add(
i18n.translate('xpack.ml.calendarsList.deleteCalendars.deletingCalendarsNotificationMessage', {
defaultMessage: 'Deleting {messageId}',
values: { messageId }
})
);
for(const calendar of calendarsToDelete) { for(const calendar of calendarsToDelete) {
const calendarId = calendar.calendar_id; const calendarId = calendar.calendar_id;
@ -25,14 +34,22 @@ export async function deleteCalendars(calendarsToDelete, callback) {
await ml.deleteCalendar({ calendarId }); await ml.deleteCalendar({ calendarId });
} catch (error) { } catch (error) {
console.log('Error deleting calendar:', error); console.log('Error deleting calendar:', error);
let errorMessage = `An error occurred deleting calendar ${calendar.calendar_id}`; const errorMessage = i18n.translate('xpack.ml.calendarsList.deleteCalendars.deletingCalendarErrorMessage', {
if (error.message) { defaultMessage: 'An error occurred deleting calendar {calendarId}{errorMessage}',
errorMessage += ` : ${error.message}`; values: {
calendarId: calendar.calendar_id,
errorMessage: error.message ? ` : ${error.message}` : ''
} }
});
toastNotifications.addDanger(errorMessage); toastNotifications.addDanger(errorMessage);
} }
} }
toastNotifications.addSuccess(`${messageId} deleted`); toastNotifications.addSuccess(
i18n.translate('xpack.ml.calendarsList.deleteCalendars.deletingCalendarSuccessNotificationMessage', {
defaultMessage: '{messageId} deleted',
values: { messageId }
})
);
callback(); callback();
} }

View file

@ -20,6 +20,8 @@ import { getCalendarManagementBreadcrumbs } from '../../breadcrumbs';
import uiRoutes from 'ui/routes'; import uiRoutes from 'ui/routes';
import { I18nProvider } from '@kbn/i18n/react';
const template = ` const template = `
<ml-nav-menu name="settings" /> <ml-nav-menu name="settings" />
<div class="mlCalendarManagement"> <div class="mlCalendarManagement">
@ -54,7 +56,9 @@ module.directive('mlCalendarsList', function () {
}; };
ReactDOM.render( ReactDOM.render(
React.createElement(CalendarsList, props), <I18nProvider>
{React.createElement(CalendarsList, props)}
</I18nProvider>,
element[0] element[0]
); );
} }

View file

@ -76,7 +76,11 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = `
size="s" size="s"
type="button" type="button"
> >
New <FormattedMessage
defaultMessage="New"
id="xpack.ml.calendarsList.table.newButtonLabel"
values={Object {}}
/>
</EuiButton>, </EuiButton>,
<EuiButton <EuiButton
color="danger" color="danger"
@ -88,7 +92,11 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = `
size="s" size="s"
type="button" type="button"
> >
Delete <FormattedMessage
defaultMessage="Delete"
id="xpack.ml.calendarsList.table.deleteButtonLabel"
values={Object {}}
/>
</EuiButton>, </EuiButton>,
], ],
} }

View file

@ -17,8 +17,10 @@ import {
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
export function CalendarsListTable({
export const CalendarsListTable = injectI18n(function CalendarsListTable({
calendarsList, calendarsList,
onDeleteClick, onDeleteClick,
setSelectedCalendarList, setSelectedCalendarList,
@ -26,7 +28,8 @@ export function CalendarsListTable({
canCreateCalendar, canCreateCalendar,
canDeleteCalendar, canDeleteCalendar,
mlNodesAvailable, mlNodesAvailable,
itemsSelected itemsSelected,
intl
}) { }) {
const sorting = { const sorting = {
@ -44,7 +47,10 @@ export function CalendarsListTable({
const columns = [ const columns = [
{ {
field: 'calendar_id', field: 'calendar_id',
name: 'ID', name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.idColumnName',
defaultMessage: 'ID'
}),
sortable: true, sortable: true,
truncateText: true, truncateText: true,
render: (id) => ( render: (id) => (
@ -57,15 +63,27 @@ export function CalendarsListTable({
}, },
{ {
field: 'job_ids_string', field: 'job_ids_string',
name: 'Jobs', name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.jobsColumnName',
defaultMessage: 'Jobs'
}),
sortable: true, sortable: true,
truncateText: true, truncateText: true,
}, },
{ {
field: 'events_length', field: 'events_length',
name: 'Events', name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.eventsColumnName',
defaultMessage: 'Events'
}),
sortable: true, sortable: true,
render: (eventsLength) => `${eventsLength} ${eventsLength === 1 ? 'event' : 'events'}` render: (eventsLength) => intl.formatMessage(
{
id: 'xpack.ml.calendarsList.table.eventsCountLabel',
defaultMessage: '{eventsLength, plural, one {# event} other {# events}}'
},
{ eventsLength }
)
} }
]; ];
@ -83,7 +101,10 @@ export function CalendarsListTable({
href={`${chrome.getBasePath()}/app/ml#/settings/calendars_list/new_calendar`} href={`${chrome.getBasePath()}/app/ml#/settings/calendars_list/new_calendar`}
isDisabled={(canCreateCalendar === false || mlNodesAvailable === false)} isDisabled={(canCreateCalendar === false || mlNodesAvailable === false)}
> >
New <FormattedMessage
id="xpack.ml.calendarsList.table.newButtonLabel"
defaultMessage="New"
/>
</EuiButton> </EuiButton>
), ),
( (
@ -94,7 +115,10 @@ export function CalendarsListTable({
onClick={onDeleteClick} onClick={onDeleteClick}
isDisabled={(canDeleteCalendar === false || mlNodesAvailable === false || itemsSelected === false)} isDisabled={(canDeleteCalendar === false || mlNodesAvailable === false || itemsSelected === false)}
> >
Delete <FormattedMessage
id="xpack.ml.calendarsList.table.deleteButtonLabel"
defaultMessage="Delete"
/>
</EuiButton> </EuiButton>
) )
], ],
@ -119,9 +143,9 @@ export function CalendarsListTable({
/> />
</React.Fragment> </React.Fragment>
); );
} });
CalendarsListTable.propTypes = { CalendarsListTable.WrappedComponent.propTypes = {
calendarsList: PropTypes.array.isRequired, calendarsList: PropTypes.array.isRequired,
onDeleteClick: PropTypes.func.isRequired, onDeleteClick: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,

View file

@ -5,7 +5,7 @@
*/ */
import { shallow, mount } from 'enzyme'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react'; import React from 'react';
import { CalendarsListTable } from './table'; import { CalendarsListTable } from './table';
@ -41,15 +41,15 @@ const props = {
describe('CalendarsListTable', () => { describe('CalendarsListTable', () => {
test('renders the table with all calendars', () => { test('renders the table with all calendars', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<CalendarsListTable {...props} /> <CalendarsListTable.WrappedComponent {...props} />
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('New button enabled if permission available', () => { test('New button enabled if permission available', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<CalendarsListTable {...props} /> <CalendarsListTable.WrappedComponent {...props} />
); );
const buttons = wrapper.find('[data-testid="new_calendar_button"]'); const buttons = wrapper.find('[data-testid="new_calendar_button"]');
@ -64,8 +64,8 @@ describe('CalendarsListTable', () => {
canCreateCalendar: false canCreateCalendar: false
}; };
const wrapper = mount( const wrapper = mountWithIntl(
<CalendarsListTable {...disableProps} /> <CalendarsListTable.WrappedComponent {...disableProps} />
); );
const buttons = wrapper.find('[data-testid="new_calendar_button"]'); const buttons = wrapper.find('[data-testid="new_calendar_button"]');
@ -81,8 +81,8 @@ describe('CalendarsListTable', () => {
mlNodesAvailable: false mlNodesAvailable: false
}; };
const wrapper = mount( const wrapper = mountWithIntl(
<CalendarsListTable {...disableProps} /> <CalendarsListTable.WrappedComponent {...disableProps} />
); );
const buttons = wrapper.find('[data-testid="new_calendar_button"]'); const buttons = wrapper.find('[data-testid="new_calendar_button"]');