Remove 'minute' frequency option from SLM policy form because ES won't allow a frequency faster than every 15 minutes. (#84854) (#85446)

* Add frequencyBlockList prop to CronEditor to allow selectively removing frequency options.
* Add unit test coverage for CronEditor component.
* Convert es_ui_shared cron editor component to TypeScript.
This commit is contained in:
CJ Cenizal 2020-12-09 12:18:23 -08:00 committed by GitHub
parent 0af9f9c9bf
commit f019d8e6b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 10253 additions and 302 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,166 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { padStart } from 'lodash';
import { EuiSelectOption } from '@elastic/eui';
import { DayOrdinal, MonthOrdinal, getOrdinalValue, getDayName, getMonthName } from './services';
import { Frequency, Field, FieldToValueMap } from './types';
type FieldFlags = {
[key in Field]?: boolean;
};
function makeSequence(min: number, max: number): number[] {
const values = [];
for (let i = min; i <= max; i++) {
values.push(i);
}
return values;
}
export const MINUTE_OPTIONS = makeSequence(0, 59).map((value) => ({
value: value.toString(),
text: padStart(value.toString(), 2, '0'),
}));
export const HOUR_OPTIONS = makeSequence(0, 23).map((value) => ({
value: value.toString(),
text: padStart(value.toString(), 2, '0'),
}));
export const DAY_OPTIONS = makeSequence(1, 7).map((value) => ({
value: value.toString(),
text: getDayName((value - 1) as DayOrdinal),
}));
export const DATE_OPTIONS = makeSequence(1, 31).map((value) => ({
value: value.toString(),
text: getOrdinalValue(value),
}));
export const MONTH_OPTIONS = makeSequence(1, 12).map((value) => ({
value: value.toString(),
text: getMonthName((value - 1) as MonthOrdinal),
}));
export const UNITS: EuiSelectOption[] = [
{
value: 'MINUTE',
text: 'minute',
},
{
value: 'HOUR',
text: 'hour',
},
{
value: 'DAY',
text: 'day',
},
{
value: 'WEEK',
text: 'week',
},
{
value: 'MONTH',
text: 'month',
},
{
value: 'YEAR',
text: 'year',
},
];
export const frequencyToFieldsMap: Record<Frequency, FieldFlags> = {
MINUTE: {},
HOUR: {
minute: true,
},
DAY: {
hour: true,
minute: true,
},
WEEK: {
day: true,
hour: true,
minute: true,
},
MONTH: {
date: true,
hour: true,
minute: true,
},
YEAR: {
month: true,
date: true,
hour: true,
minute: true,
},
};
export const frequencyToBaselineFieldsMap: Record<Frequency, FieldToValueMap> = {
MINUTE: {
second: '0',
minute: '*',
hour: '*',
date: '*',
month: '*',
day: '?',
},
HOUR: {
second: '0',
minute: '0',
hour: '*',
date: '*',
month: '*',
day: '?',
},
DAY: {
second: '0',
minute: '0',
hour: '0',
date: '*',
month: '*',
day: '?',
},
WEEK: {
second: '0',
minute: '0',
hour: '0',
date: '?',
month: '*',
day: '7',
},
MONTH: {
second: '0',
minute: '0',
hour: '0',
date: '1',
month: '*',
day: '?',
},
YEAR: {
second: '0',
minute: '0',
hour: '0',
date: '1',
month: '1',
day: '?',
},
};

View file

@ -18,13 +18,25 @@
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
interface Props {
minute?: string;
minuteOptions: EuiSelectOption[];
hour?: string;
hourOptions: EuiSelectOption[];
onChange: ({ minute, hour }: { minute?: string; hour?: string }) => void;
}
export const CronDaily = ({ minute, minuteOptions, hour, hourOptions, onChange }) => (
export const CronDaily: React.FunctionComponent<Props> = ({
minute,
minuteOptions,
hour,
hourOptions,
onChange,
}) => (
<Fragment>
<EuiFormRow
label={
@ -67,11 +79,3 @@ export const CronDaily = ({ minute, minuteOptions, hour, hourOptions, onChange }
</EuiFormRow>
</Fragment>
);
CronDaily.propTypes = {
minute: PropTypes.string.isRequired,
minuteOptions: PropTypes.array.isRequired,
hour: PropTypes.string.isRequired,
hourOptions: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -0,0 +1,137 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import sinon from 'sinon';
import { findTestSubject } from '@elastic/eui/lib/test';
import { mountWithI18nProvider } from '@kbn/test/jest';
import { Frequency } from './types';
import { CronEditor } from './cron_editor';
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
return {
htmlIdGenerator: () => () => `generated-id`,
};
});
describe('CronEditor', () => {
['MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'].forEach((unit) => {
test(`is rendered with a ${unit} frequency`, () => {
const component = mountWithI18nProvider(
<CronEditor
fieldToPreferredValueMap={{}}
frequency={unit as Frequency}
cronExpression="0 10 * * * ?"
onChange={() => {}}
/>
);
expect(component).toMatchSnapshot();
});
});
describe('props', () => {
describe('frequencyBlockList', () => {
it('excludes the blocked frequencies from the frequency list', () => {
const component = mountWithI18nProvider(
<CronEditor
frequencyBlockList={['HOUR', 'WEEK', 'YEAR']}
fieldToPreferredValueMap={{}}
frequency={'WEEK'}
cronExpression="0 10 * * * ?"
onChange={() => {}}
/>
);
const frequencySelect = findTestSubject(component, 'cronFrequencySelect');
expect(frequencySelect.text()).toBe('minutedaymonth');
});
});
describe('cronExpression', () => {
it('sets the values of the fields', () => {
const component = mountWithI18nProvider(
<CronEditor
fieldToPreferredValueMap={{}}
frequency={'YEAR'}
cronExpression="0 20 10 5 2 ?"
onChange={() => {}}
/>
);
const monthSelect = findTestSubject(component, 'cronFrequencyYearlyMonthSelect');
expect(monthSelect.props().value).toBe('2');
const dateSelect = findTestSubject(component, 'cronFrequencyYearlyDateSelect');
expect(dateSelect.props().value).toBe('5');
const hourSelect = findTestSubject(component, 'cronFrequencyYearlyHourSelect');
expect(hourSelect.props().value).toBe('10');
const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect');
expect(minuteSelect.props().value).toBe('20');
});
});
describe('onChange', () => {
it('is called when the frequency changes', () => {
const onChangeSpy = sinon.spy();
const component = mountWithI18nProvider(
<CronEditor
fieldToPreferredValueMap={{}}
frequency={'YEAR'}
cronExpression="0 10 * * * ?"
onChange={onChangeSpy}
/>
);
const frequencySelect = findTestSubject(component, 'cronFrequencySelect');
frequencySelect.simulate('change', { target: { value: 'MONTH' } });
sinon.assert.calledWith(onChangeSpy, {
cronExpression: '0 0 0 1 * ?',
fieldToPreferredValueMap: {},
frequency: 'MONTH',
});
});
it(`is called when a field's value changes`, () => {
const onChangeSpy = sinon.spy();
const component = mountWithI18nProvider(
<CronEditor
fieldToPreferredValueMap={{}}
frequency={'YEAR'}
cronExpression="0 10 * * * ?"
onChange={onChangeSpy}
/>
);
const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect');
minuteSelect.simulate('change', { target: { value: '40' } });
sinon.assert.calledWith(onChangeSpy, {
cronExpression: '0 40 * * * ?',
fieldToPreferredValueMap: { minute: '40' },
frequency: 'YEAR',
});
});
});
});
});

View file

@ -18,207 +18,86 @@
*/
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { padStart } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiSelect, EuiFormRow, EuiSelectOption } from '@elastic/eui';
import { EuiSelect, EuiFormRow } from '@elastic/eui';
import { Frequency, Field, FieldToValueMap } from './types';
import {
getOrdinalValue,
getDayName,
getMonthName,
cronExpressionToParts,
cronPartsToExpression,
MINUTE,
HOUR,
DAY,
WEEK,
MONTH,
YEAR,
} from './services';
MINUTE_OPTIONS,
HOUR_OPTIONS,
DAY_OPTIONS,
DATE_OPTIONS,
MONTH_OPTIONS,
UNITS,
frequencyToFieldsMap,
frequencyToBaselineFieldsMap,
} from './constants';
import { cronExpressionToParts, cronPartsToExpression } from './services';
import { CronHourly } from './cron_hourly';
import { CronDaily } from './cron_daily';
import { CronWeekly } from './cron_weekly';
import { CronMonthly } from './cron_monthly';
import { CronYearly } from './cron_yearly';
function makeSequence(min, max) {
const values = [];
for (let i = min; i <= max; i++) {
values.push(i);
const excludeBlockListedFrequencies = (
units: EuiSelectOption[],
blockListedUnits: string[] = []
): EuiSelectOption[] => {
if (blockListedUnits.length === 0) {
return units;
}
return values;
return units.filter(({ value }) => !blockListedUnits.includes(value as string));
};
interface Props {
frequencyBlockList?: string[];
fieldToPreferredValueMap: FieldToValueMap;
frequency: Frequency;
cronExpression: string;
onChange: ({
cronExpression,
fieldToPreferredValueMap,
frequency,
}: {
cronExpression: string;
fieldToPreferredValueMap: FieldToValueMap;
frequency: Frequency;
}) => void;
}
const MINUTE_OPTIONS = makeSequence(0, 59).map((value) => ({
value: value.toString(),
text: padStart(value, 2, '0'),
}));
type State = FieldToValueMap;
const HOUR_OPTIONS = makeSequence(0, 23).map((value) => ({
value: value.toString(),
text: padStart(value, 2, '0'),
}));
const DAY_OPTIONS = makeSequence(1, 7).map((value) => ({
value: value.toString(),
text: getDayName(value - 1),
}));
const DATE_OPTIONS = makeSequence(1, 31).map((value) => ({
value: value.toString(),
text: getOrdinalValue(value),
}));
const MONTH_OPTIONS = makeSequence(1, 12).map((value) => ({
value: value.toString(),
text: getMonthName(value - 1),
}));
const UNITS = [
{
value: MINUTE,
text: 'minute',
},
{
value: HOUR,
text: 'hour',
},
{
value: DAY,
text: 'day',
},
{
value: WEEK,
text: 'week',
},
{
value: MONTH,
text: 'month',
},
{
value: YEAR,
text: 'year',
},
];
const frequencyToFieldsMap = {
[MINUTE]: {},
[HOUR]: {
minute: true,
},
[DAY]: {
hour: true,
minute: true,
},
[WEEK]: {
day: true,
hour: true,
minute: true,
},
[MONTH]: {
date: true,
hour: true,
minute: true,
},
[YEAR]: {
month: true,
date: true,
hour: true,
minute: true,
},
};
const frequencyToBaselineFieldsMap = {
[MINUTE]: {
second: '0',
minute: '*',
hour: '*',
date: '*',
month: '*',
day: '?',
},
[HOUR]: {
second: '0',
minute: '0',
hour: '*',
date: '*',
month: '*',
day: '?',
},
[DAY]: {
second: '0',
minute: '0',
hour: '0',
date: '*',
month: '*',
day: '?',
},
[WEEK]: {
second: '0',
minute: '0',
hour: '0',
date: '?',
month: '*',
day: '7',
},
[MONTH]: {
second: '0',
minute: '0',
hour: '0',
date: '1',
month: '*',
day: '?',
},
[YEAR]: {
second: '0',
minute: '0',
hour: '0',
date: '1',
month: '1',
day: '?',
},
};
export class CronEditor extends Component {
static propTypes = {
fieldToPreferredValueMap: PropTypes.object.isRequired,
frequency: PropTypes.string.isRequired,
cronExpression: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
};
static getDerivedStateFromProps(props) {
export class CronEditor extends Component<Props, State> {
static getDerivedStateFromProps(props: Props) {
const { cronExpression } = props;
return cronExpressionToParts(cronExpression);
}
constructor(props) {
constructor(props: Props) {
super(props);
const { cronExpression } = props;
const parsedCron = cronExpressionToParts(cronExpression);
this.state = {
...parsedCron,
};
}
onChangeFrequency = (frequency) => {
onChangeFrequency = (frequency: Frequency) => {
const { onChange, fieldToPreferredValueMap } = this.props;
// Update fields which aren't editable with acceptable baseline values.
const editableFields = Object.keys(frequencyToFieldsMap[frequency]);
const inheritedFields = editableFields.reduce(
(baselineFields, field) => {
const editableFields = Object.keys(frequencyToFieldsMap[frequency]) as Field[];
const inheritedFields = editableFields.reduce<FieldToValueMap>(
(fieldBaselines, field) => {
if (fieldToPreferredValueMap[field] != null) {
baselineFields[field] = fieldToPreferredValueMap[field];
fieldBaselines[field] = fieldToPreferredValueMap[field];
}
return baselineFields;
return fieldBaselines;
},
{ ...frequencyToBaselineFieldsMap[frequency] }
);
@ -232,18 +111,21 @@ export class CronEditor extends Component {
});
};
onChangeFields = (fields) => {
onChangeFields = (fields: FieldToValueMap) => {
const { onChange, frequency, fieldToPreferredValueMap } = this.props;
const editableFields = Object.keys(frequencyToFieldsMap[frequency]);
const newFieldToPreferredValueMap = {};
const editableFields = Object.keys(frequencyToFieldsMap[frequency]) as Field[];
const newFieldToPreferredValueMap: FieldToValueMap = {};
const editedFields = editableFields.reduce(
const editedFields = editableFields.reduce<FieldToValueMap>(
(accumFields, field) => {
if (fields[field] !== undefined) {
accumFields[field] = fields[field];
// Once the user touches a field, we want to persist its value as the user changes
// the cron frequency.
// If the user changes a field's value, we want to maintain that value in the relevant
// field, even as the frequency field changes. For example, if the user selects "Monthly"
// frequency and changes the "Hour" field to "10", that field should still say "10" if the
// user changes the frequency to "Weekly". We'll support this UX by storing these values
// in the fieldToPreferredValueMap.
newFieldToPreferredValueMap[field] = fields[field];
} else {
accumFields[field] = this.state[field];
@ -271,10 +153,10 @@ export class CronEditor extends Component {
const { minute, hour, day, date, month } = this.state;
switch (frequency) {
case MINUTE:
case 'MINUTE':
return;
case HOUR:
case 'HOUR':
return (
<CronHourly
minute={minute}
@ -283,7 +165,7 @@ export class CronEditor extends Component {
/>
);
case DAY:
case 'DAY':
return (
<CronDaily
minute={minute}
@ -294,7 +176,7 @@ export class CronEditor extends Component {
/>
);
case WEEK:
case 'WEEK':
return (
<CronWeekly
minute={minute}
@ -307,7 +189,7 @@ export class CronEditor extends Component {
/>
);
case MONTH:
case 'MONTH':
return (
<CronMonthly
minute={minute}
@ -320,7 +202,7 @@ export class CronEditor extends Component {
/>
);
case YEAR:
case 'YEAR':
return (
<CronYearly
minute={minute}
@ -341,7 +223,7 @@ export class CronEditor extends Component {
}
render() {
const { frequency } = this.props;
const { frequency, frequencyBlockList } = this.props;
return (
<Fragment>
@ -352,9 +234,11 @@ export class CronEditor extends Component {
fullWidth
>
<EuiSelect
options={UNITS}
options={excludeBlockListedFrequencies(UNITS, frequencyBlockList)}
value={frequency}
onChange={(e) => this.onChangeFrequency(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
this.onChangeFrequency(e.target.value as Frequency)
}
fullWidth
prepend={i18n.translate('esUi.cronEditor.textEveryLabel', {
defaultMessage: 'Every',

View file

@ -18,13 +18,17 @@
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
import { EuiFormRow, EuiSelect } from '@elastic/eui';
interface Props {
minute?: string;
minuteOptions: EuiSelectOption[];
onChange: ({ minute }: { minute?: string }) => void;
}
export const CronHourly = ({ minute, minuteOptions, onChange }) => (
export const CronHourly: React.FunctionComponent<Props> = ({ minute, minuteOptions, onChange }) => (
<Fragment>
<EuiFormRow
label={
@ -46,9 +50,3 @@ export const CronHourly = ({ minute, minuteOptions, onChange }) => (
</EuiFormRow>
</Fragment>
);
CronHourly.propTypes = {
minute: PropTypes.string.isRequired,
minuteOptions: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -18,13 +18,21 @@
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
interface Props {
minute?: string;
minuteOptions: EuiSelectOption[];
hour?: string;
hourOptions: EuiSelectOption[];
date?: string;
dateOptions: EuiSelectOption[];
onChange: ({ minute, hour, date }: { minute?: string; hour?: string; date?: string }) => void;
}
export const CronMonthly = ({
export const CronMonthly: React.FunctionComponent<Props> = ({
minute,
minuteOptions,
hour,
@ -94,13 +102,3 @@ export const CronMonthly = ({
</EuiFormRow>
</Fragment>
);
CronMonthly.propTypes = {
minute: PropTypes.string.isRequired,
minuteOptions: PropTypes.array.isRequired,
hour: PropTypes.string.isRequired,
hourOptions: PropTypes.array.isRequired,
date: PropTypes.string.isRequired,
dateOptions: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -18,13 +18,21 @@
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
interface Props {
minute?: string;
minuteOptions: EuiSelectOption[];
hour?: string;
hourOptions: EuiSelectOption[];
day?: string;
dayOptions: EuiSelectOption[];
onChange: ({ minute, hour, day }: { minute?: string; hour?: string; day?: string }) => void;
}
export const CronWeekly = ({
export const CronWeekly: React.FunctionComponent<Props> = ({
minute,
minuteOptions,
hour,
@ -94,13 +102,3 @@ export const CronWeekly = ({
</EuiFormRow>
</Fragment>
);
CronWeekly.propTypes = {
minute: PropTypes.string.isRequired,
minuteOptions: PropTypes.array.isRequired,
hour: PropTypes.string.isRequired,
hourOptions: PropTypes.array.isRequired,
day: PropTypes.string.isRequired,
dayOptions: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -18,13 +18,34 @@
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui';
export const CronYearly = ({
interface Props {
minute?: string;
minuteOptions: EuiSelectOption[];
hour?: string;
hourOptions: EuiSelectOption[];
date?: string;
dateOptions: EuiSelectOption[];
month?: string;
monthOptions: EuiSelectOption[];
onChange: ({
minute,
hour,
date,
month,
}: {
minute?: string;
hour?: string;
date?: string;
month?: string;
}) => void;
}
export const CronYearly: React.FunctionComponent<Props> = ({
minute,
minuteOptions,
hour,
@ -115,15 +136,3 @@ export const CronYearly = ({
</EuiFormRow>
</Fragment>
);
CronYearly.propTypes = {
minute: PropTypes.string.isRequired,
minuteOptions: PropTypes.array.isRequired,
hour: PropTypes.string.isRequired,
hourOptions: PropTypes.array.isRequired,
date: PropTypes.string.isRequired,
dateOptions: PropTypes.array.isRequired,
month: PropTypes.string.isRequired,
monthOptions: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -17,5 +17,5 @@
* under the License.
*/
export { Frequency } from './types';
export { CronEditor } from './cron_editor';
export { MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from './services';

View file

@ -17,15 +17,10 @@
* under the License.
*/
export const MINUTE = 'MINUTE';
export const HOUR = 'HOUR';
export const DAY = 'DAY';
export const WEEK = 'WEEK';
export const MONTH = 'MONTH';
export const YEAR = 'YEAR';
import { FieldToValueMap } from '../types';
export function cronExpressionToParts(expression) {
const parsedCron = {
export function cronExpressionToParts(expression: string): FieldToValueMap {
const parsedCron: FieldToValueMap = {
second: undefined,
minute: undefined,
hour: undefined,
@ -63,6 +58,13 @@ export function cronExpressionToParts(expression) {
return parsedCron;
}
export function cronPartsToExpression({ second, minute, hour, day, date, month }) {
export function cronPartsToExpression({
second,
minute,
hour,
day,
date,
month,
}: FieldToValueMap): string {
return `${second} ${minute} ${hour} ${date} ${month} ${day}`;
}

View file

@ -19,6 +19,9 @@
import { i18n } from '@kbn/i18n';
export type DayOrdinal = 0 | 1 | 2 | 3 | 4 | 5 | 6;
export type MonthOrdinal = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
// The international ISO standard dictates Monday as the first day of the week, but cron patterns
// use Sunday as the first day, so we're going with the cron way.
const dayOrdinalToDayNameMap = {
@ -46,7 +49,7 @@ const monthOrdinalToMonthNameMap = {
11: i18n.translate('esUi.cronEditor.month.december', { defaultMessage: 'December' }),
};
export function getOrdinalValue(number) {
export function getOrdinalValue(number: number): string {
// TODO: This is breaking reporting pdf generation. Possibly due to phantom not setting locale,
// which is needed by i18n (formatjs). Need to verify, fix, and restore i18n in place of static stings.
// return i18n.translate('esUi.cronEditor.number.ordinal', {
@ -57,15 +60,16 @@ export function getOrdinalValue(number) {
// Protects against falsey (including 0) values
const num = number && number.toString();
let lastDigit = num && num.substr(-1);
const lastDigitString = num && num.substr(-1);
let ordinal;
if (!lastDigit) {
return number;
if (!lastDigitString) {
return number.toString();
}
lastDigit = parseFloat(lastDigit);
switch (lastDigit) {
const lastDigitNumeric = parseFloat(lastDigitString);
switch (lastDigitNumeric) {
case 1:
ordinal = 'st';
break;
@ -82,10 +86,10 @@ export function getOrdinalValue(number) {
return `${num}${ordinal}`;
}
export function getDayName(dayOrdinal) {
export function getDayName(dayOrdinal: DayOrdinal): string {
return dayOrdinalToDayNameMap[dayOrdinal];
}
export function getMonthName(monthOrdinal) {
export function getMonthName(monthOrdinal: MonthOrdinal): string {
return monthOrdinalToMonthNameMap[monthOrdinal];
}

View file

@ -17,5 +17,11 @@
* under the License.
*/
export * from './cron';
export * from './humanized_numbers';
export { cronExpressionToParts, cronPartsToExpression } from './cron';
export {
getOrdinalValue,
getDayName,
getMonthName,
DayOrdinal,
MonthOrdinal,
} from './humanized_numbers';

View file

@ -17,10 +17,8 @@
* under the License.
*/
export declare const MINUTE: string;
export declare const HOUR: string;
export declare const DAY: string;
export declare const WEEK: string;
export declare const MONTH: string;
export declare const YEAR: string;
export declare const CronEditor: any;
export type Frequency = 'MINUTE' | 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR';
export type Field = 'second' | 'minute' | 'hour' | 'day' | 'date' | 'month';
export type FieldToValueMap = {
[key in Field]?: string;
};

View file

@ -30,7 +30,7 @@ export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './compon
export { SectionLoading } from './components/section_loading';
export { CronEditor, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from './components/cron_editor';
export { Frequency, CronEditor } from './components/cron_editor';
export {
SendRequestConfig,

View file

@ -4,14 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
MINUTE,
HOUR,
DAY,
WEEK,
MONTH,
YEAR,
} from '../../../../../../src/plugins/es_ui_shared/public';
import { indexPatterns } from '../../../../../../src/plugins/data/public';
import { setHttp } from '../../crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
@ -190,14 +182,14 @@ describe('Create Rollup Job, step 1: Logistics', () => {
describe('every minute', () => {
it('should not have any additional configuration', () => {
changeFrequency(MINUTE);
changeFrequency('MINUTE');
expect(find('cronFrequencyConfiguration').length).toBe(0);
});
});
describe('hourly', () => {
beforeEach(() => {
changeFrequency(HOUR);
changeFrequency('HOUR');
});
it('should have 1 additional configuration', () => {
@ -214,7 +206,7 @@ describe('Create Rollup Job, step 1: Logistics', () => {
describe('daily', () => {
beforeEach(() => {
changeFrequency(DAY);
changeFrequency('DAY');
});
it('should have 1 additional configuration with hour and minute selects', () => {
@ -238,7 +230,7 @@ describe('Create Rollup Job, step 1: Logistics', () => {
describe('weekly', () => {
beforeEach(() => {
changeFrequency(WEEK);
changeFrequency('WEEK');
});
it('should have 2 additional configurations with day, hour and minute selects', () => {
@ -277,7 +269,7 @@ describe('Create Rollup Job, step 1: Logistics', () => {
describe('monthly', () => {
beforeEach(() => {
changeFrequency(MONTH);
changeFrequency('MONTH');
});
it('should have 2 additional configurations with date, hour and minute selects', () => {
@ -308,7 +300,7 @@ describe('Create Rollup Job, step 1: Logistics', () => {
describe('yearly', () => {
beforeEach(() => {
changeFrequency(YEAR);
changeFrequency('YEAR');
});
it('should have 3 additional configurations with month, date, hour and minute selects', () => {

View file

@ -23,7 +23,7 @@ import {
} from '@elastic/eui';
import { Repository } from '../../../../../common/types';
import { CronEditor, SectionError } from '../../../../shared_imports';
import { Frequency, CronEditor, SectionError } from '../../../../shared_imports';
import { useServices } from '../../../app_context';
import { DEFAULT_POLICY_SCHEDULE, DEFAULT_POLICY_FREQUENCY } from '../../../constants';
import { useLoadRepositories } from '../../../services/http';
@ -71,7 +71,7 @@ export const PolicyStepLogistics: React.FunctionComponent<StepProps> = ({
// State for cron editor
const [simpleCron, setSimpleCron] = useState<{
expression: string;
frequency: string;
frequency: Frequency;
}>({
expression: DEFAULT_POLICY_SCHEDULE,
frequency: DEFAULT_POLICY_FREQUENCY,
@ -480,6 +480,7 @@ export const PolicyStepLogistics: React.FunctionComponent<StepProps> = ({
) : (
<Fragment>
<CronEditor
frequencyBlockList={['MINUTE']} // ES disallows a frequency faster than 15m
fieldToPreferredValueMap={fieldToPreferredValueMap}
cronExpression={simpleCron.expression}
frequency={simpleCron.frequency}
@ -487,10 +488,6 @@ export const PolicyStepLogistics: React.FunctionComponent<StepProps> = ({
cronExpression: expression,
frequency,
fieldToPreferredValueMap: newFieldToPreferredValueMap,
}: {
cronExpression: string;
frequency: string;
fieldToPreferredValueMap: any;
}) => {
setSimpleCron({
expression,

View file

@ -25,7 +25,7 @@ import {
import { useServices, useToastNotifications } from '../app_context';
import { documentationLinksService } from '../services/documentation';
import { CronEditor } from '../../shared_imports';
import { Frequency, CronEditor } from '../../shared_imports';
import { DEFAULT_RETENTION_SCHEDULE, DEFAULT_RETENTION_FREQUENCY } from '../constants';
import { updateRetentionSchedule } from '../services/http';
@ -57,7 +57,7 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent<Props
const [simpleCron, setSimpleCron] = useState<{
expression: string;
frequency: string;
frequency: Frequency;
}>({
expression: DEFAULT_RETENTION_SCHEDULE,
frequency: DEFAULT_RETENTION_FREQUENCY,
@ -234,10 +234,6 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent<Props
cronExpression: expression,
frequency,
fieldToPreferredValueMap: newFieldToPreferredValueMap,
}: {
cronExpression: string;
frequency: string;
fieldToPreferredValueMap: any;
}) => {
setSimpleCron({
expression,

View file

@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { DAY } from '../../shared_imports';
export const BASE_PATH = '';
export const DEFAULT_SECTION: Section = 'snapshots';
export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policies';
@ -89,10 +87,10 @@ export const REMOVE_INDEX_SETTINGS_SUGGESTIONS: string[] = INDEX_SETTING_SUGGEST
);
export const DEFAULT_POLICY_SCHEDULE = '0 30 1 * * ?';
export const DEFAULT_POLICY_FREQUENCY = DAY;
export const DEFAULT_POLICY_FREQUENCY = 'DAY';
export const DEFAULT_RETENTION_SCHEDULE = '0 30 1 * * ?';
export const DEFAULT_RETENTION_FREQUENCY = DAY;
export const DEFAULT_RETENTION_FREQUENCY = 'DAY';
// UI Metric constants
export const UIM_APP_NAME = 'snapshot_restore';

View file

@ -7,8 +7,8 @@
export {
AuthorizationProvider,
CronEditor,
DAY,
Error,
Frequency,
NotAuthorizedSection,
SectionError,
sendRequest,