Skip to content

Commit

Permalink
[Workplace Search] Update Source Frequency views to match new designs (
Browse files Browse the repository at this point in the history
…#114157) (#114212)

* Add schema for blocked_windows and blockedWindows type

* Update constants and types

Also changes the size of a spacer

* Update FrequencyItem to new design

* Update BlockedWindowItem to new design

* Update logic file for new designs

It was decided that we would omit the seconds from any API-submitted duration values.

* Add i18n for UTC tooltip

* Better function name

Also moved const closer to function declarations for easier readability

* Add reducers

CI was complaining about types since the method wasn’t used. Was going to add these in a future PR but will add them here so we can merge

Co-authored-by: Scotty Bollinger <[email protected]>
  • Loading branch information
kibanamachine and scottybollinger authored Oct 7, 2021
1 parent f27ec3f commit e489302
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

import { i18n } from '@kbn/i18n';

export const ALL_DAYS_LABEL = i18n.translate('xpack.enterpriseSearch.units.allDaysLabel', {
defaultMessage: 'All days',
});

export const MINUTES_UNIT_LABEL = i18n.translate('xpack.enterpriseSearch.units.minutesLabel', {
defaultMessage: 'Minutes',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ const defaultIndexing = {
rules: [],
schedule: {
full: 'P1D',
incremental: 'P2H',
delete: 'P10M',
permissions: 'P3H',
incremental: 'PT2H',
delete: 'PT10M',
permissions: 'PT3H',
estimates: {
full: {
nextStart: '2021-09-30T15:37:38+00:00',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,8 @@ export const BETWEEN_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSea
defaultMessage: 'between',
});

export const EVERY_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.everyLabel', {
defaultMessage: 'every',
export const ON_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.onLabel', {
defaultMessage: 'on',
});

export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@ interface SyncIndexItem<T> {
permissions?: T;
}

interface IndexingSchedule extends SyncIndexItem<string> {
export interface IndexingSchedule extends SyncIndexItem<string> {
estimates: SyncIndexItem<SyncEstimate>;
blockedWindows?: BlockedWindow[];
}

export type SyncJobType = 'full' | 'incremental' | 'delete' | 'permissions';
Expand All @@ -162,7 +163,7 @@ export type DayOfWeek = typeof DAYS_OF_WEEK_VALUES[number];

export interface BlockedWindow {
jobType: SyncJobType;
day: DayOfWeek;
day: DayOfWeek | 'all';
start: Moment;
end: Moment;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import React from 'react';

import { shallow } from 'enzyme';

import { EuiComboBox, EuiDatePicker, EuiSuperSelect } from '@elastic/eui';
import { EuiDatePickerRange, EuiSelect, EuiSuperSelect } from '@elastic/eui';

import { BlockedWindowItem } from './blocked_window_item';

Expand All @@ -20,8 +20,8 @@ describe('BlockedWindowItem', () => {
it('renders', () => {
const wrapper = shallow(<BlockedWindowItem {...props} />);

expect(wrapper.find(EuiComboBox)).toHaveLength(1);
expect(wrapper.find(EuiSelect)).toHaveLength(1);
expect(wrapper.find(EuiSuperSelect)).toHaveLength(1);
expect(wrapper.find(EuiDatePicker)).toHaveLength(2);
expect(wrapper.find(EuiDatePickerRange)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@

import React from 'react';

import moment from 'moment';

import {
EuiButton,
EuiComboBox,
EuiDatePicker,
EuiDatePickerRange,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiSelect,
EuiSelectOption,
EuiSpacer,
EuiSuperSelect,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import { DAYS_OF_WEEK_LABELS } from '../../../../../shared/constants';
import { BLOCK_LABEL, BETWEEN_LABEL, EVERY_LABEL, AND, REMOVE_BUTTON } from '../../../../constants';
import { ALL_DAYS_LABEL, DAYS_OF_WEEK_LABELS } from '../../../../../shared/constants';
import { BLOCK_LABEL, BETWEEN_LABEL, ON_LABEL, REMOVE_BUTTON } from '../../../../constants';
import { BlockedWindow, DAYS_OF_WEEK_VALUES } from '../../../../types';

import {
Expand All @@ -31,6 +37,7 @@ import {
INCREMENTAL_SYNC_DESCRIPTION,
DELETION_SYNC_DESCRIPTION,
PERMISSIONS_SYNC_DESCRIPTION,
UTC_TITLE,
} from '../../constants';

interface Props {
Expand Down Expand Up @@ -80,10 +87,11 @@ const syncOptions = [
},
];

const dayPickerOptions = DAYS_OF_WEEK_VALUES.map((day) => ({
label: DAYS_OF_WEEK_LABELS[day.toUpperCase() as keyof typeof DAYS_OF_WEEK_LABELS],
const daySelectOptions = DAYS_OF_WEEK_VALUES.map((day) => ({
text: DAYS_OF_WEEK_LABELS[day.toUpperCase() as keyof typeof DAYS_OF_WEEK_LABELS],
value: day,
}));
})) as EuiSelectOption[];
daySelectOptions.push({ text: ALL_DAYS_LABEL, value: 'all' });

export const BlockedWindowItem: React.FC<Props> = ({ blockedWindow }) => {
const handleSyncTypeChange = () => '#TODO';
Expand All @@ -99,46 +107,59 @@ export const BlockedWindowItem: React.FC<Props> = ({ blockedWindow }) => {
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 175 }} className="blockedItemSyncSelect">
<EuiSuperSelect
valueOfSelected={'permissions'}
valueOfSelected={blockedWindow.jobType}
options={syncOptions}
onChange={handleSyncTypeChange}
itemClassName="blockedWindowSelectItem"
popoverClassName="blockedWindowSelectPopover"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>{BETWEEN_LABEL}</EuiText>
<EuiText>{ON_LABEL}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 128 }}>
<EuiDatePicker
showTimeSelect
showTimeSelectOnly
selected={blockedWindow.start}
onChange={handleStartDateChange}
dateFormat="hh:mm A"
timeFormat="hh:mm A"
/>
<EuiFlexItem style={{ minWidth: 130 }}>
<EuiSelect value={blockedWindow.day} options={daySelectOptions} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>{AND}</EuiText>
<EuiText>{BETWEEN_LABEL}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 128 }}>
<EuiDatePicker
showTimeSelect
showTimeSelectOnly
selected={blockedWindow.end}
onChange={handleEndDateChange}
dateFormat="hh:mm A"
timeFormat="hh:mm A"
<EuiFlexItem grow={false}>
<EuiDatePickerRange
startDateControl={
<EuiDatePicker
showTimeSelect
showTimeSelectOnly
selected={moment(blockedWindow.start, 'HH:mm:ssZ')}
onChange={handleStartDateChange}
dateFormat="h:mm A"
timeFormat="h:mm A"
/>
}
endDateControl={
<EuiDatePicker
showTimeSelect
showTimeSelectOnly
selected={moment(blockedWindow.end, 'HH:mm:ssZ')}
onChange={handleEndDateChange}
dateFormat="h:mm A"
timeFormat="h:mm A"
/>
}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>{EVERY_LABEL}</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiComboBox
selectedOptions={[dayPickerOptions[0], dayPickerOptions[1]]}
options={dayPickerOptions}
<EuiIconTip
title={UTC_TITLE}
type="iInCircle"
content={
<EuiText size="s">
<FormattedMessage
id="xpack.enterpriseSearch.workplaceSearch.sources.utcLabel"
defaultMessage="Current UTC time: {utcTime}"
values={{ utcTime: moment().utc().format('h:mm A') }}
/>
</EuiText>
}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const Frequency: React.FC<FrequencyProps> = ({ tabId }) => {
action={actions}
/>
{docsLinks}
<EuiSpacer size="s" />
<EuiSpacer />
<EuiTabbedContent tabs={tabs} selectedTab={tabs[tabId]} onTabClick={onSelectedTabChanged} />
</SourceLayout>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import moment from 'moment';

import { EuiFieldNumber, EuiSuperSelect } from '@elastic/eui';
import { EuiFieldNumber } from '@elastic/eui';

import { FrequencyItem } from './frequency_item';

Expand All @@ -31,37 +31,26 @@ describe('FrequencyItem', () => {
it('renders', () => {
const wrapper = shallow(<FrequencyItem {...props} />);

expect(wrapper.find(EuiSuperSelect)).toHaveLength(1);
expect(wrapper.find(EuiFieldNumber)).toHaveLength(1);
expect(wrapper.find(EuiFieldNumber)).toHaveLength(3);
});

describe('ISO8601 formatting', () => {
it('handles minutes display', () => {
const wrapper = shallow(<FrequencyItem {...props} duration="P1DT2H3M4S" />);

expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(1563);
expect(wrapper.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('minutes');
expect(wrapper.find('[data-test-subj="durationMinutes"]').prop('value')).toEqual(3);
});

it('handles hours display', () => {
const wrapper = shallow(<FrequencyItem {...props} duration="P1DT2H" />);

expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(26);
expect(wrapper.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('hours');
expect(wrapper.find('[data-test-subj="durationHours"]').prop('value')).toEqual(2);
});

it('handles days display', () => {
const wrapper = shallow(<FrequencyItem {...props} duration="P3D" />);

expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(3);
expect(wrapper.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('days');
});

it('handles seconds display (defaults to 1 minute)', () => {
const wrapper = shallow(<FrequencyItem {...props} duration="P44S" />);

expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(1);
expect(wrapper.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('minutes');
expect(wrapper.find('[data-test-subj="durationDays"]').prop('value')).toEqual(3);
});

it('handles "nextStart" that is in past', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
EuiFlexItem,
EuiIconTip,
EuiSpacer,
EuiSuperSelect,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
Expand All @@ -36,30 +35,12 @@ interface Props {
estimate: SyncEstimate;
}

const unitOptions = [
{
value: 'minutes',
inputDisplay: MINUTES_UNIT_LABEL,
},
{
value: 'hours',
inputDisplay: HOURS_UNIT_LABEL,
},
{
value: 'days',
inputDisplay: DAYS_UNIT_LABEL,
},
];

export const FrequencyItem: React.FC<Props> = ({ label, description, duration, estimate }) => {
const [interval, unit] = formatDuration(duration);
const { lastRun, nextStart, duration: durationEstimate } = estimate;
const estimateDisplay = durationEstimate && moment.duration(durationEstimate).humanize();
const nextStartIsPast = moment().isAfter(nextStart);
const nextStartTime = nextStartIsPast ? NEXT_SYNC_RUNNING_MESSAGE : moment(nextStart).fromNow();

const onChange = () => '#TODO';

const frequencyItemLabel = (
<FormattedMessage
id="xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.frequencyItemLabel"
Expand Down Expand Up @@ -121,11 +102,26 @@ export const FrequencyItem: React.FC<Props> = ({ label, description, duration, e
<EuiFlexItem grow={false}>
<EuiText>{frequencyItemLabel}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 90 }}>
<EuiFieldNumber value={interval} />
<EuiFlexItem grow={false} style={{ width: 120 }}>
<EuiFieldNumber
data-test-subj="durationDays"
value={moment.duration(duration).days()}
append={DAYS_UNIT_LABEL}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 120 }}>
<EuiFieldNumber
data-test-subj="durationHours"
value={moment.duration(duration).hours()}
append={HOURS_UNIT_LABEL}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 128 }}>
<EuiSuperSelect valueOfSelected={unit} options={unitOptions} onChange={onChange} />
<EuiFlexItem grow={false} style={{ width: 150 }}>
<EuiFieldNumber
data-test-subj="durationMinutes"
value={moment.duration(duration).minutes()}
append={MINUTES_UNIT_LABEL}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip title={label} type="iInCircle" content={description} />
Expand All @@ -139,24 +135,3 @@ export const FrequencyItem: React.FC<Props> = ({ label, description, duration, e
</>
);
};

// In most cases, the user will use the form to set the sync frequency, in which case the duration
// will be in the format of "PT3D" (ISO 8601). However, if an operator has set the sync frequency via
// the API, the duration could be a complex format, such as "P1DT2H3M4S". It was decided that in this
// case, we should omit seconds and go with the least common denominator from minutes.
//
// Example: "P1DT2H3M4S" -> "1563 Minutes"
const formatDuration = (duration: string): [interval: number, unit: string] => {
const momentDuration = moment.duration(duration);
if (duration.includes('M')) {
return [Math.round(momentDuration.asMinutes()), unitOptions[0].value];
}
if (duration.includes('H')) {
return [Math.round(momentDuration.asHours()), unitOptions[1].value];
}
if (duration.includes('D')) {
return [Math.round(momentDuration.asDays()), unitOptions[2].value];
}

return [1, unitOptions[0].value];
};
Loading

0 comments on commit e489302

Please sign in to comment.