[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"
verticalPosition="center"
>
<CalendarForm
<InjectIntl(CalendarForm)
calendarId=""
canCreateCalendar={true}
canDeleteCalendar={true}

View file

@ -1,5 +1,28 @@
// 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`] = `
<EuiForm>
<React.Fragment>
@ -7,14 +30,19 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []}
error={
Array [
"Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores;
must start and end with an alphanumeric character",
"Use lowercase alphanumerics (a-z and 0-9), hyphens or underscores; must start and end with an alphanumeric character",
]
}
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={true}
label="Calendar ID"
label={
<FormattedMessage
defaultMessage="Calendar ID"
id="xpack.ml.calendarsEdit.calendarForm.calendarIdLabel"
values={Object {}}
/>
}
>
<EuiFieldText
compressed={false}
@ -30,7 +58,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Description"
label={
<FormattedMessage
defaultMessage="Description"
id="xpack.ml.calendarsEdit.calendarForm.descriptionLabel"
values={Object {}}
/>
}
>
<EuiFieldText
compressed={false}
@ -47,7 +81,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Jobs"
label={
<FormattedMessage
defaultMessage="Jobs"
id="xpack.ml.calendarsEdit.calendarForm.jobsLabel"
values={Object {}}
/>
}
>
<EuiComboBox
compressed={false}
@ -64,7 +104,13 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Groups"
label={
<FormattedMessage
defaultMessage="Groups"
id="xpack.ml.calendarsEdit.calendarForm.groupsLabel"
values={Object {}}
/>
}
>
<EuiComboBox
compressed={false}
@ -85,9 +131,15 @@ exports[`CalendarForm Renders calendar form 1`] = `
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={false}
label="Events"
label={
<FormattedMessage
defaultMessage="Events"
id="xpack.ml.calendarsEdit.calendarForm.eventsLabel"
values={Object {}}
/>
}
>
<EventsTable
<InjectIntl(EventsTable)
canCreateCalendar={true}
canDeleteCalendar={true}
eventsList={Array []}
@ -122,7 +174,11 @@ exports[`CalendarForm Renders calendar form 1`] = `
onClick={[MockFunction]}
type="button"
>
Save
<FormattedMessage
defaultMessage="Save"
id="xpack.ml.calendarsEdit.calendarForm.saveButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem
@ -137,7 +193,11 @@ exports[`CalendarForm Renders calendar form 1`] = `
isDisabled={false}
type="button"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
id="xpack.ml.calendarsEdit.calendarForm.cancelButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,9 +27,16 @@ import {
import { ImportedEvents } from '../imported_events';
import { readFile, parseICSFile, filterEvents } from './utils';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
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) {
super(props);
@ -45,7 +52,10 @@ export class ImportModal extends Component {
handleImport = async (loadedFile) => {
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 = [];
if (incomingFile && incomingFile.size <= (MAX_FILE_SIZE_MB * 1000000)) {
@ -107,7 +117,7 @@ export class ImportModal extends Component {
);
render() {
const { closeImportModal } = this.props;
const { closeImportModal, intl } = this.props;
const {
fileLoading,
fileLoaded,
@ -143,11 +153,19 @@ export class ImportModal extends Component {
>
<EuiFlexItem grow={false}>
<EuiModalHeaderTitle >
Import events
<FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importEventsTitle"
defaultMessage="Import events"
/>
</EuiModalHeaderTitle>
</EuiFlexItem>
<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>
</EuiFlexGroup>
</EuiModalHeader>
@ -157,7 +175,10 @@ export class ImportModal extends Component {
<EuiFlexItem grow={false}>
<EuiFilePicker
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}
disabled={fileLoading}
/>
@ -182,21 +203,22 @@ export class ImportModal extends Component {
fill
disabled={fileLoaded === false || errorMessage !== null}
>
Import
<FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.importButtonLabel"
defaultMessage="Import"
/>
</EuiButton>
<EuiButtonEmpty
onClick={closeImportModal}
>
Cancel
<FormattedMessage
id="xpack.ml.calendarsEdit.eventsTable.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiModalFooter>
</EuiModal>
</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 { ImportModal } from './import_modal';
@ -34,16 +34,16 @@ const events = [{
describe('ImportModal', () => {
test('Renders import modal', () => {
const wrapper = shallow(
<ImportModal {...testProps}/>
const wrapper = shallowWithIntl(
<ImportModal.WrappedComponent {...testProps}/>
);
expect(wrapper).toMatchSnapshot();
});
test('Deletes selected event from event table', () => {
const wrapper = mount(
<ImportModal {...testProps} />
const wrapper = mountWithIntl(
<ImportModal.WrappedComponent {...testProps} />
);
const testState = {

View file

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

View file

@ -13,6 +13,7 @@ import {
EuiSpacer
} from '@elastic/eui';
import { EventsTable } from '../events_table/';
import { FormattedMessage } from '@kbn/i18n/react';
export function ImportedEvents({
@ -27,10 +28,21 @@ export function ImportedEvents({
<EuiSpacer size="s"/>
<EuiFlexItem>
<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 && (
<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>
@ -45,7 +57,10 @@ export function ImportedEvents({
<EuiFlexItem grow={false}>
<EuiCheckbox
id="ml-include-past-events"
label="Include past events"
label={<FormattedMessage
id="xpack.ml.calendarsEdit.importedEvents.includePastEventsLabel"
defaultMessage="Include past events"
/>}
checked={includePastEvents}
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 { ImportedEvents } from './imported_events';
@ -33,7 +33,7 @@ const testProps = {
describe('ImportedEvents', () => {
test('Renders imported events', () => {
const wrapper = shallow(
const wrapper = shallowWithIntl(
<ImportedEvents {...testProps} />
);

View file

@ -25,7 +25,15 @@ import { ImportModal } from './import_modal';
import { ml } from '../../../services/ml_api_service';
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) {
super(props);
this.state = {
@ -99,7 +107,12 @@ export class NewCalendar extends Component {
} catch (error) {
console.log(error);
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 () => {
const { formCalendarId } = this.state;
const { intl } = this.props;
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 {
const calendar = this.setUpCalendarForApi();
this.setState({ saving: true });
@ -130,7 +152,15 @@ export class NewCalendar extends Component {
} catch (error) {
console.log('Error saving calendar', error);
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) {
console.log('Error saving calendar', error);
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>
);
}
}
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 { NewCalendar } from './new_calendar';
@ -84,16 +84,16 @@ const props = {
describe('NewCalendar', () => {
test('Renders new calendar form', () => {
const wrapper = shallow(
<NewCalendar {...props}/>
const wrapper = shallowWithIntl(
<NewCalendar.WrappedComponent {...props}/>
);
expect(wrapper).toMatchSnapshot();
});
test('Import modal shown on Import Events button click', () => {
const wrapper = mount(
<NewCalendar {...props}/>
const wrapper = mountWithIntl(
<NewCalendar.WrappedComponent {...props}/>
);
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', () => {
const wrapper = mount(
<NewCalendar {...props}/>
const wrapper = mountWithIntl(
<NewCalendar.WrappedComponent {...props}/>
);
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', () => {
const wrapper = mount(
<NewCalendar {...props}/>
const wrapper = mountWithIntl(
<NewCalendar.WrappedComponent {...props}/>
);
const instance = wrapper.instance();
@ -135,8 +135,8 @@ describe('NewCalendar', () => {
canCreateCalendar: false,
};
const wrapper = mount(
<NewCalendar {...noCreateProps} />
const wrapper = mountWithIntl(
<NewCalendar.WrappedComponent {...noCreateProps} />
);
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 { generateTempId } from '../utils';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
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) {
super(props);
@ -158,11 +165,19 @@ export class NewEventModal extends Component {
endDateString,
} = this.state;
const { intl } = this.props;
const timeInputs = (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow label="From:" helpText={TIME_FORMAT}>
<EuiFormRow
label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.fromLabel"
defaultMessage="From:"
/>}
helpText={TIME_FORMAT}
>
<EuiFieldText
name="startTime"
onChange={this.handleTimeStartChange}
@ -172,7 +187,13 @@ export class NewEventModal extends Component {
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="To:" helpText={TIME_FORMAT}>
<EuiFormRow
label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.toLabel"
defaultMessage="To:"
/>}
helpText={TIME_FORMAT}
>
<EuiFieldText
name="endTime"
onChange={this.handleTimeEndChange}
@ -203,7 +224,10 @@ export class NewEventModal extends Component {
startDate={startDate}
endDate={endDate}
isInvalid={startDate > endDate}
aria-label="Start date"
aria-label={intl.formatMessage({
id: 'xpack.ml.calendarsEdit.newEventModal.startDateAriaLabel',
defaultMessage: 'Start date'
})}
timeFormat={TIME_FORMAT}
dateFormat={TIME_FORMAT}
/>
@ -217,7 +241,10 @@ export class NewEventModal extends Component {
startDate={startDate}
endDate={endDate}
isInvalid={startDate > endDate}
aria-label="End date"
aria-label={intl.formatMessage({
id: 'xpack.ml.calendarsEdit.newEventModal.endDateAriaLabel',
defaultMessage: 'End date'
})}
timeFormat={TIME_FORMAT}
dateFormat={TIME_FORMAT}
/>
@ -241,14 +268,20 @@ export class NewEventModal extends Component {
>
<EuiModalHeader>
<EuiModalHeaderTitle >
Create new event
<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.createNewEventTitle"
defaultMessage="Create new event"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiForm>
<EuiFormRow
label="Description"
label={<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.descriptionLabel"
defaultMessage="Description"
/>}
fullWidth
>
<EuiFieldText
@ -269,21 +302,22 @@ export class NewEventModal extends Component {
fill
disabled={!description}
>
Add
<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.addButtonLabel"
defaultMessage="Add"
/>
</EuiButton>
<EuiButtonEmpty
onClick={closeModal}
>
Cancel
<FormattedMessage
id="xpack.ml.calendarsEdit.newEventModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiModalFooter>
</EuiModal>
</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 { NewEventModal } from './new_event_modal';
import moment from 'moment';
@ -24,8 +24,8 @@ const stateTimestamps = {
describe('NewEventModal', () => {
it('Add button disabled if description empty', () => {
const wrapper = shallow(
<NewEventModal {...testProps} />
const wrapper = shallowWithIntl(
<NewEventModal.WrappedComponent {...testProps} />
);
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', () => {
const wrapper = shallow(<NewEventModal {...testProps} />);
const wrapper = shallowWithIntl(<NewEventModal.WrappedComponent {...testProps} />);
const instance = wrapper.instance();
instance.setState({
startDate: moment(stateTimestamps.startDate),
@ -53,7 +53,7 @@ describe('NewEventModal', () => {
});
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();
instance.setState({
startDate: moment(stateTimestamps.startDate),

View file

@ -8,6 +8,7 @@
import { ml } from '../../../services/ml_api_service';
import { isJobIdValid } from '../../../../common/util/job_utils';
import { i18n } from '@kbn/i18n';
function getJobIds() {
@ -17,7 +18,10 @@ function getJobIds() {
resolve(resp.map((job) => job.id));
})
.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);
reject(errorMessage);
});
@ -31,7 +35,10 @@ function getGroupIds() {
resolve(resp.map((group) => group.id));
})
.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);
reject(errorMessage);
});
@ -45,7 +52,10 @@ function getCalendars() {
resolve(resp);
})
.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);
reject(errorMessage);
});

View file

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

View file

@ -24,8 +24,14 @@ import { ml } from '../../../services/ml_api_service';
import { toastNotifications } from 'ui/notify';
import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
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) {
super(props);
this.state = {
@ -50,7 +56,12 @@ export class CalendarsList extends Component {
} catch (error) {
console.log(error);
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 = (
<EuiOverlayMask>
<EuiConfirmModal
title="Delete calendar"
title={<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarTitle"
defaultMessage="Delete calendar"
/>}
onCancel={this.closeDestroyModal}
onConfirm={this.deleteCalendars}
cancelButtonText="Cancel"
confirmButtonText="Delete"
cancelButtonText={<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.cancelButtonLabel"
defaultMessage="Cancel"
/>}
confirmButtonText={<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.deleteButtonLabel"
defaultMessage="Delete"
/>}
buttonColor="danger"
defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
>
<p>
{
`Delete ${selectedForDeletion.length === 1 ? 'this' : 'these'}
calendar${selectedForDeletion.length === 1 ? '' : 's'}?
${selectedForDeletion.map((c) => c.calendar_id).join(', ')}`
}
<FormattedMessage
id="xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarsDescription"
defaultMessage="Delete {calendarsCount, plural, one {this calendar} other {these calendars}}? {calendarsList}"
values={{
calendarsCount: selectedForDeletion.length,
calendarsList: (selectedForDeletion.map((c) => c.calendar_id).join(', '))
}}
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
@ -142,9 +165,4 @@ export class CalendarsList extends Component {
</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 { ml } from '../../../services/ml_api_service';
@ -78,16 +78,16 @@ describe('CalendarsList', () => {
test('loads calendars on mount', () => {
ml.calendars = jest.fn();
shallow(
<CalendarsList {...props}/>
shallowWithIntl(
<CalendarsList.WrappedComponent {...props}/>
);
expect(ml.calendars).toHaveBeenCalled();
});
test('Renders calendar list with calendars', () => {
const wrapper = shallow(
<CalendarsList {...props}/>
const wrapper = shallowWithIntl(
<CalendarsList.WrappedComponent {...props}/>
);
wrapper.instance().setState(testingState);
@ -96,8 +96,8 @@ describe('CalendarsList', () => {
});
test('Sets selected calendars list on checkbox change', () => {
const wrapper = mount(
<CalendarsList {...props}/>
const wrapper = mountWithIntl(
<CalendarsList.WrappedComponent {...props}/>
);
const instance = wrapper.instance();

View file

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

View file

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

View file

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

View file

@ -17,8 +17,10 @@ import {
import chrome from 'ui/chrome';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
export function CalendarsListTable({
export const CalendarsListTable = injectI18n(function CalendarsListTable({
calendarsList,
onDeleteClick,
setSelectedCalendarList,
@ -26,7 +28,8 @@ export function CalendarsListTable({
canCreateCalendar,
canDeleteCalendar,
mlNodesAvailable,
itemsSelected
itemsSelected,
intl
}) {
const sorting = {
@ -44,7 +47,10 @@ export function CalendarsListTable({
const columns = [
{
field: 'calendar_id',
name: 'ID',
name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.idColumnName',
defaultMessage: 'ID'
}),
sortable: true,
truncateText: true,
render: (id) => (
@ -57,15 +63,27 @@ export function CalendarsListTable({
},
{
field: 'job_ids_string',
name: 'Jobs',
name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.jobsColumnName',
defaultMessage: 'Jobs'
}),
sortable: true,
truncateText: true,
},
{
field: 'events_length',
name: 'Events',
name: intl.formatMessage({
id: 'xpack.ml.calendarsList.table.eventsColumnName',
defaultMessage: 'Events'
}),
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`}
isDisabled={(canCreateCalendar === false || mlNodesAvailable === false)}
>
New
<FormattedMessage
id="xpack.ml.calendarsList.table.newButtonLabel"
defaultMessage="New"
/>
</EuiButton>
),
(
@ -94,7 +115,10 @@ export function CalendarsListTable({
onClick={onDeleteClick}
isDisabled={(canDeleteCalendar === false || mlNodesAvailable === false || itemsSelected === false)}
>
Delete
<FormattedMessage
id="xpack.ml.calendarsList.table.deleteButtonLabel"
defaultMessage="Delete"
/>
</EuiButton>
)
],
@ -119,9 +143,9 @@ export function CalendarsListTable({
/>
</React.Fragment>
);
}
});
CalendarsListTable.propTypes = {
CalendarsListTable.WrappedComponent.propTypes = {
calendarsList: PropTypes.array.isRequired,
onDeleteClick: PropTypes.func.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 { CalendarsListTable } from './table';
@ -41,15 +41,15 @@ const props = {
describe('CalendarsListTable', () => {
test('renders the table with all calendars', () => {
const wrapper = shallow(
<CalendarsListTable {...props} />
const wrapper = shallowWithIntl(
<CalendarsListTable.WrappedComponent {...props} />
);
expect(wrapper).toMatchSnapshot();
});
test('New button enabled if permission available', () => {
const wrapper = mount(
<CalendarsListTable {...props} />
const wrapper = mountWithIntl(
<CalendarsListTable.WrappedComponent {...props} />
);
const buttons = wrapper.find('[data-testid="new_calendar_button"]');
@ -64,8 +64,8 @@ describe('CalendarsListTable', () => {
canCreateCalendar: false
};
const wrapper = mount(
<CalendarsListTable {...disableProps} />
const wrapper = mountWithIntl(
<CalendarsListTable.WrappedComponent {...disableProps} />
);
const buttons = wrapper.find('[data-testid="new_calendar_button"]');
@ -81,8 +81,8 @@ describe('CalendarsListTable', () => {
mlNodesAvailable: false
};
const wrapper = mount(
<CalendarsListTable {...disableProps} />
const wrapper = mountWithIntl(
<CalendarsListTable.WrappedComponent {...disableProps} />
);
const buttons = wrapper.find('[data-testid="new_calendar_button"]');