[Rollup] Client integration test for job creation (#32223) (#33232)

This commit is contained in:
Sébastien Loix 2019-03-14 13:58:21 +01:00 committed by GitHub
parent ba55349d27
commit 59436fb735
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1711 additions and 73 deletions

View file

@ -0,0 +1,146 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import axios from 'axios';
import { registerTestBed } from '../utils';
import { rollupJobsStore } from '../../public/crud_app/store';
import {
setHttp,
} from '../../public/crud_app/services';
import { JobCreate } from '../../public/crud_app/sections';
// axios has a $http like interface so using it to simulate $http
setHttp(axios.create());
// This is the Rollup job we will be creating in our tests
const JOB_TO_CREATE = {
id: 'test-job',
indexPattern: 'test-pattern-*',
rollupIndex: 'rollup-index',
interval: '24h'
};
const initUserActions = (component, findTestSubject) => {
const clickNextStep = () => {
const button = findTestSubject('rollupJobNextButton');
button.simulate('click');
component.update();
};
const clickPreviousStep = () => {
const button = findTestSubject('rollupJobBackButton');
button.simulate('click');
component.update();
};
const clickSave = () => {
const button = findTestSubject('rollupJobSaveButton');
button.simulate('click');
component.update();
};
return {
clickNextStep,
clickPreviousStep,
clickSave,
};
};
const initFillFormFields = form => async (step) => {
switch (step) {
case 'logistics':
form.setInputValue('rollupJobName', JOB_TO_CREATE.id);
await form.setInputValue('rollupIndexPattern', JOB_TO_CREATE.indexPattern, true);
form.setInputValue('rollupIndexName', JOB_TO_CREATE.rollupIndex);
break;
case 'date-histogram':
form.setInputValue('rollupJobInterval', JOB_TO_CREATE.interval);
break;
default:
return;
}
};
const initGoToStep = (fillFormFields, clickNextStep) => async (targetStep) => {
const stepHandlers = {
1: () => fillFormFields('logistics'),
2: () => fillFormFields('date-histogram')
};
let currentStep = 1;
while(currentStep < targetStep) {
if (stepHandlers[currentStep]) {
await stepHandlers[currentStep]();
}
clickNextStep();
currentStep++;
}
};
export const initTestBed = () => {
const testBed = registerTestBed(JobCreate, {}, rollupJobsStore)();
const userActions = initUserActions(testBed.component, testBed.findTestSubject);
const fillFormFields = initFillFormFields(testBed.form);
const goToStep = initGoToStep(fillFormFields, userActions.clickNextStep);
const getEuiStepsHorizontalActive = () => testBed.component.find('.euiStepHorizontal-isSelected').text();
return {
...testBed,
userActions: {
...userActions
},
form: {
...testBed.form,
fillFormFields,
},
goToStep,
getEuiStepsHorizontalActive,
};
};
export const nextTick = async () => new Promise((resolve) => setTimeout(resolve));
export const mockServerResponses = server => {
const mockIndexPatternValidityResponse = (response) => {
const defaultResponse = {
doesMatchIndices: true,
doesMatchRollupIndices: false,
dateFields: ['foo', 'bar'],
numericFields: [],
keywordFields: [],
};
server.respondWith(/\/api\/rollup\/index_pattern_validity\/.*/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({ ...defaultResponse, ...response }),
]);
};
const mockCreateJob = () => {
server.respondWith(/\/api\/rollup\/create/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({}),
]);
};
const mockUserActions = () => {
server.respondWith(/\/api\/user_action\/.*/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({}),
]);
};
mockIndexPatternValidityResponse();
mockCreateJob();
mockUserActions();
return { mockIndexPatternValidityResponse };
};

View file

@ -0,0 +1,137 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import moment from 'moment-timezone';
import { initTestBed, mockServerResponses } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: () => '/api/rollup',
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 2: Date histogram', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let getFormErrorsMessages;
let form;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
let goToStep;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getFormErrorsMessages,
form,
getEuiStepsHorizontalActive,
goToStep,
} = initTestBed());
});
afterEach(() => {
server.restore();
});
describe('layout', () => {
beforeEach(async () => {
await goToStep(2);
});
it('should have the horizontal step active on "Date histogram"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Date histogram');
});
it('should have the title set to "Date histogram"', () => {
expect(testSubjectExists('rollupJobCreateDateHistogramTitle')).toBe(true);
});
it('should have a link to the documentation', () => {
expect(testSubjectExists('rollupJobCreateDateHistogramDocsButton')).toBe(true);
});
it('should have the "next" and "back" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(true);
expect(testSubjectExists('rollupJobNextButton')).toBe(true);
expect(testSubjectExists('rollupJobSaveButton')).toBe(false);
});
it('should go to the "Logistics" step when clicking the back button', async () => {
userActions.clickPreviousStep();
expect(getEuiStepsHorizontalActive()).toContain('Logistics');
});
});
describe('Date field select', () => {
it('should set the options value from the index pattern', async () => {
const dateFields = ['field1', 'field2', 'field3'];
mockIndexPatternValidityResponse({ dateFields });
await goToStep(2);
const dateFieldSelectOptionsValues = findTestSubject('rollupJobCreateDateFieldSelect').find('option').map(option => option.text());
expect(dateFieldSelectOptionsValues).toEqual(dateFields);
});
});
describe('time zone', () => {
it('should have a select with all the timezones', async () => {
await goToStep(2);
const timeZoneSelect = findTestSubject('rollupJobCreateTimeZoneSelect');
const options = timeZoneSelect.find('option').map(option => option.text());
expect(options).toEqual(moment.tz.names());
});
});
describe('form validation', () => {
beforeEach(async () => {
await goToStep(2);
});
it('should display errors when clicking "next" without filling the form', () => {
expect(testSubjectExists('rollupJobCreateStepError')).toBeFalsy();
userActions.clickNextStep();
expect(testSubjectExists('rollupJobCreateStepError')).toBeTruthy();
expect(getFormErrorsMessages()).toEqual(['Interval is required.']);
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
describe('interval', () => {
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should validate the interval format', () => {
form.setInputValue('rollupJobInterval', 'abc');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Invalid interval format.');
});
it('should validate the calendar format', () => {
form.setInputValue('rollupJobInterval', '3y');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain(`The 'y' unit only allows values of 1. Try 1y.`);
});
});
});
});

View file

@ -0,0 +1,237 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import { initTestBed, mockServerResponses } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: () => '/api/rollup',
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 4: Histogram', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
let goToStep;
let getMetadataFromEuiTable;
let form;
let getFormErrorsMessages;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getEuiStepsHorizontalActive,
goToStep,
getMetadataFromEuiTable,
form,
getFormErrorsMessages,
} = initTestBed());
});
afterEach(() => {
server.restore();
});
const numericFields = ['a-numericField', 'b-numericField'];
const goToStepAndOpenFieldChooser = async () => {
await goToStep(4);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
};
describe('layout', () => {
beforeEach(async () => {
await goToStep(4);
});
it('should have the horizontal step active on "Histogram"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Histogram');
});
it('should have the title set to "Terms"', () => {
expect(testSubjectExists('rollupJobCreateHistogramTitle')).toBe(true);
});
it('should have a link to the documentation', () => {
expect(testSubjectExists('rollupJobCreateHistogramDocsButton')).toBe(true);
});
it('should have the "next" and "back" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(true);
expect(testSubjectExists('rollupJobNextButton')).toBe(true);
expect(testSubjectExists('rollupJobSaveButton')).toBe(false);
});
it('should go to the "Terms" step when clicking the back button', async () => {
userActions.clickPreviousStep();
expect(getEuiStepsHorizontalActive()).toContain('Terms');
});
it('should go to the "Metrics" step when clicking the next button', async () => {
userActions.clickNextStep();
expect(getEuiStepsHorizontalActive()).toContain('Metrics');
});
it('should have a button to display the list of histogram fields to chose from', () => {
expect(testSubjectExists('rollupJobHistogramFieldChooser')).toBe(false);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
expect(testSubjectExists('rollupJobHistogramFieldChooser')).toBe(true);
});
});
describe('histogram field chooser (flyout)', () => {
describe('layout', () => {
beforeEach(async () => {
await goToStepAndOpenFieldChooser();
});
it('should have the title set to "Add histogram fields"', async () => {
expect(findTestSubject('rollupJobCreateFlyoutTitle').text()).toEqual('Add histogram fields');
});
it('should have a button to close the flyout', () => {
expect(testSubjectExists('rollupJobHistogramFieldChooser')).toBe(true);
findTestSubject('euiFlyoutCloseButton').simulate('click');
expect(testSubjectExists('rollupJobHistogramFieldChooser')).toBe(false);
});
});
describe('when no histogram fields are availalbe', () => {
it('should indicate it to the user', async () => {
mockIndexPatternValidityResponse({ numericFields: [] });
await goToStepAndOpenFieldChooser();
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobHistogramFieldChooser-table');
expect(tableCellsValues).toEqual([['No items found']]);
});
});
describe('when histogram fields are available', () => {
beforeEach(async () => {
mockIndexPatternValidityResponse({ numericFields });
await goToStepAndOpenFieldChooser();
});
it('should display the histogram fields available', async () => {
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobHistogramFieldChooser-table');
expect(tableCellsValues).toEqual([
['a-numericField'],
['b-numericField'],
]);
});
it('should add histogram field to the field list when clicking on it', () => {
let { tableCellsValues } = getMetadataFromEuiTable('rollupJobHistogramFieldList');
expect(tableCellsValues).toEqual([['No histogram fields added']]); // make sure the field list is empty
const { rows } = getMetadataFromEuiTable('rollupJobHistogramFieldChooser-table');
rows[0].reactWrapper.simulate('click'); // Select first row
({ tableCellsValues } = getMetadataFromEuiTable('rollupJobHistogramFieldList'));
const [firstRow] = tableCellsValues;
expect(firstRow[0]).toEqual('a-numericField');
});
});
});
describe('fields list', () => {
it('should have an empty field list', async () => {
await goToStep(4);
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobHistogramFieldList');
expect(tableCellsValues).toEqual([['No histogram fields added']]);
});
it('should have a delete button on each row to remove an histogram field', async () => {
// First let's add a term to the list
mockIndexPatternValidityResponse({ numericFields });
await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = getMetadataFromEuiTable('rollupJobHistogramFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click');
// Make sure rows value has been set
let { rows: fieldListRows } = getMetadataFromEuiTable('rollupJobHistogramFieldList');
expect(fieldListRows[0].columns[0].value).not.toEqual('No histogram fields added');
const columnsFirstRow = fieldListRows[0].columns;
// The last column is the eui "actions" column
const deleteButton = columnsFirstRow[columnsFirstRow.length - 1].reactWrapper.find('button');
deleteButton.simulate('click');
({ rows: fieldListRows } = getMetadataFromEuiTable('rollupJobHistogramFieldList'));
expect(fieldListRows[0].columns[0].value).toEqual('No histogram fields added');
});
});
describe('interval', () => {
const addHistogramFieldToList = () => {
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
const { rows } = getMetadataFromEuiTable('rollupJobHistogramFieldChooser-table');
rows[0].reactWrapper.simulate('click');
};
beforeEach(async () => {
mockIndexPatternValidityResponse({ numericFields });
await goToStep(4);
addHistogramFieldToList();
});
describe('input validation', () => {
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should display errors when clicking "next" without filling the interval', () => {
expect(testSubjectExists('rollupJobCreateStepError')).toBeFalsy();
userActions.clickNextStep();
expect(testSubjectExists('rollupJobCreateStepError')).toBeTruthy();
expect(getFormErrorsMessages()).toEqual(['Interval must be a whole number.']);
});
it('should be a whole number', () => {
form.setInputValue('rollupJobCreateHistogramInterval', 5.5);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toEqual(['Interval must be a whole number.']);
});
it('should be greater than zero', () => {
form.setInputValue('rollupJobCreateHistogramInterval', -1);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toEqual(['Interval must be greater than zero.']);
});
});
it('should go to next "Metrics" step if value is valid', () => {
form.setInputValue('rollupJobCreateHistogramInterval', 3);
userActions.clickNextStep();
expect(getEuiStepsHorizontalActive()).toContain('Metrics');
});
});
});

View file

@ -0,0 +1,430 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import { MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from '../../public/crud_app/services';
import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../src/legacy/ui/public/index_patterns';
import { initTestBed, mockServerResponses } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: () => '/api/rollup',
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 1: Logistics', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let getFormErrorsMessages;
let form;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getFormErrorsMessages,
form,
getEuiStepsHorizontalActive,
} = initTestBed());
});
afterEach(() => {
server.restore();
});
it('should have the horizontal step active on "Logistics"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Logistics');
});
it('should have the title set to "Logistics"', () => {
expect(testSubjectExists('rollupJobCreateLogisticsTitle')).toBe(true);
});
it('should have a link to the documentation', () => {
expect(testSubjectExists('rollupJobCreateLogisticsDocsButton')).toBe(true);
});
it('should only have the "next" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(false);
expect(testSubjectExists('rollupJobNextButton')).toBe(true);
expect(testSubjectExists('rollupJobSaveButton')).toBe(false);
});
it('should display errors when clicking "next" without filling the form', () => {
expect(testSubjectExists('rollupJobCreateStepError')).toBeFalsy();
userActions.clickNextStep();
expect(testSubjectExists('rollupJobCreateStepError')).toBeTruthy();
expect(getFormErrorsMessages()).toEqual([
'Name is required.',
'Index pattern is required.',
'Rollup index is required.',
]);
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
describe('form validations', () => {
describe('index pattern', () => {
beforeEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(false);
});
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should not allow spaces', async () => {
await form.setInputValue('rollupIndexPattern', 'with space', true);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Remove the spaces from your index pattern.');
});
it('should not allow an unknown index pattern', async () => {
mockIndexPatternValidityResponse({ doesMatchIndices: false });
await form.setInputValue('rollupIndexPattern', 'unknown', true);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Index pattern doesn\'t match any indices.');
});
it('should not allow an index pattern without time fields', async () => {
mockIndexPatternValidityResponse({ dateFields: [] });
await form.setInputValue('rollupIndexPattern', 'abc', true);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Index pattern must match indices that contain time fields.');
});
it('should not allow an index pattern that matches a rollup index', async () => {
mockIndexPatternValidityResponse({ doesMatchRollupIndices: true });
await form.setInputValue('rollupIndexPattern', 'abc', true);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Index pattern must not match rollup indices.');
});
it('should not be the same as the rollup index name', async () => {
await form.setInputValue('rollupIndexPattern', 'abc', true);
await form.setInputValue('rollupIndexName', 'abc', true);
userActions.clickNextStep();
const errorMessages = getFormErrorsMessages();
expect(errorMessages).toContain('Index pattern cannot have the same as the rollup index.');
expect(errorMessages).toContain('Rollup index cannot have the same as the index pattern.');
});
});
describe('rollup index name', () => {
beforeEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(false);
});
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should not allow spaces', () => {
form.setInputValue('rollupIndexName', 'with space');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Remove the spaces from your rollup index name.');
});
it('should not allow invalid characters', () => {
const expectInvalidChar = (char) => {
form.setInputValue('rollupIndexName', `rollup_index_${char}`);
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain(`Remove the characters ${char} from your rollup index name.`);
};
[...INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, ','].reduce((promise, char) => {
return promise.then(() => expectInvalidChar(char));
}, Promise.resolve());
});
it('should not allow a dot as first character', () => {
form.setInputValue('rollupIndexName', '.kibana');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Index names cannot begin with periods.');
});
});
describe('rollup cron', () => {
const changeFrequency = (value) => {
findTestSubject('rollupJobCreateFrequencySelect').simulate('change', { target: { value } });
};
const generateStringSequenceOfNumbers = (total) => (
new Array(total).fill('').map((_, i) => i < 10 ? `0${i}` : i.toString())
);
describe('frequency', () => {
it('should allow "minute", "hour", "day", "week", "month", "year"', () => {
const frequencySelect = findTestSubject('rollupJobCreateFrequencySelect');
const options = frequencySelect.find('option').map(option => option.text());
expect(options).toEqual(['minute', 'hour', 'day', 'week', 'month', 'year']);
});
describe('every minute', () => {
it('should not have any additional configuration', () => {
changeFrequency(MINUTE);
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(0);
});
});
describe('hourly', () => {
beforeEach(() => {
changeFrequency(HOUR);
});
it('should have 1 additional configuration', () => {
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(1);
expect(testSubjectExists('rollupJobCreateFrequencyHourlyMinuteSelect')).toBe(true);
});
it('should allow to select any minute from 00 -> 59', () => {
const minutSelect = findTestSubject('rollupJobCreateFrequencyHourlyMinuteSelect');
const options = minutSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(60));
});
});
describe('daily', () => {
beforeEach(() => {
changeFrequency(DAY);
});
it('should have 1 additional configuration with hour and minute selects', () => {
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(1);
expect(testSubjectExists('rollupJobCreateFrequencyDailyHourSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyDailyMinuteSelect')).toBe(true);
});
it('should allow to select any hour from 00 -> 23', () => {
const hourSelect = findTestSubject('rollupJobCreateFrequencyDailyHourSelect');
const options = hourSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(24));
});
it('should allow to select any miute from 00 -> 59', () => {
const minutSelect = findTestSubject('rollupJobCreateFrequencyDailyMinuteSelect');
const options = minutSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(60));
});
});
describe('weekly', () => {
beforeEach(() => {
changeFrequency(WEEK);
});
it('should have 2 additional configurations with day, hour and minute selects', () => {
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(2);
expect(testSubjectExists('rollupJobCreateFrequencyWeeklyDaySelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyWeeklyHourSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyWeeklyMinuteSelect')).toBe(true);
});
it('should allow to select any day of the week', () => {
const hourSelect = findTestSubject('rollupJobCreateFrequencyWeeklyDaySelect');
const options = hourSelect.find('option').map(option => option.text());
expect(options).toEqual([
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
]);
});
it('should allow to select any hour from 00 -> 23', () => {
const hourSelect = findTestSubject('rollupJobCreateFrequencyWeeklyHourSelect');
const options = hourSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(24));
});
it('should allow to select any miute from 00 -> 59', () => {
const minutSelect = findTestSubject('rollupJobCreateFrequencyWeeklyMinuteSelect');
const options = minutSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(60));
});
});
describe('monthly', () => {
beforeEach(() => {
changeFrequency(MONTH);
});
it('should have 2 additional configurations with date, hour and minute selects', () => {
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(2);
expect(testSubjectExists('rollupJobCreateFrequencyMonthlyDateSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyMonthlyHourSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyMonthlyMinuteSelect')).toBe(true);
});
it('should allow to select any date of the month from 1st to 31st', () => {
const dateSelect = findTestSubject('rollupJobCreateFrequencyMonthlyDateSelect');
const options = dateSelect.find('option').map(option => option.text());
expect(options.length).toEqual(31);
});
it('should allow to select any hour from 00 -> 23', () => {
const hourSelect = findTestSubject('rollupJobCreateFrequencyMonthlyHourSelect');
const options = hourSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(24));
});
it('should allow to select any miute from 00 -> 59', () => {
const minutSelect = findTestSubject('rollupJobCreateFrequencyMonthlyMinuteSelect');
const options = minutSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(60));
});
});
describe('yearly', () => {
beforeEach(() => {
changeFrequency(YEAR);
});
it('should have 3 additional configurations with month, date, hour and minute selects', () => {
expect(findTestSubject('rollupCronFrequencyConfiguration').length).toBe(3);
expect(testSubjectExists('rollupJobCreateFrequencyYearlyMonthSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyYearlyDateSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyYearlyHourSelect')).toBe(true);
expect(testSubjectExists('rollupJobCreateFrequencyYearlyMinuteSelect')).toBe(true);
});
it('should allow to select any month of the year', () => {
const monthSelect = findTestSubject('rollupJobCreateFrequencyYearlyMonthSelect');
const options = monthSelect.find('option').map(option => option.text());
expect(options).toEqual([
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]);
});
it('should allow to select any date of the month from 1st to 31st', () => {
const dateSelect = findTestSubject('rollupJobCreateFrequencyYearlyDateSelect');
const options = dateSelect.find('option').map(option => option.text());
expect(options.length).toEqual(31);
});
it('should allow to select any hour from 00 -> 23', () => {
const hourSelect = findTestSubject('rollupJobCreateFrequencyYearlyHourSelect');
const options = hourSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(24));
});
it('should allow to select any miute from 00 -> 59', () => {
const minutSelect = findTestSubject('rollupJobCreateFrequencyYearlyMinuteSelect');
const options = minutSelect.find('option').map(option => option.text());
expect(options).toEqual(generateStringSequenceOfNumbers(60));
});
});
});
describe('advanced cron expression', () => {
const activateAdvancedCronExpression = () => {
findTestSubject('rollupShowAdvancedCronLink').simulate('click');
};
it('should allow to create a cron expression', () => {
expect(testSubjectExists('rollupAdvancedCron')).toBe(false);
activateAdvancedCronExpression();
expect(testSubjectExists('rollupAdvancedCron')).toBe(true);
});
it('should not be empty', () => {
activateAdvancedCronExpression();
form.setInputValue('rollupAdvancedCron', '');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Cron pattern or basic interval is required.');
});
it('should not allow unvalid expression', () => {
activateAdvancedCronExpression();
form.setInputValue('rollupAdvancedCron', 'invalid');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Expression has only 1 part. At least 5 parts are required.');
});
});
});
describe('page size', () => {
beforeEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(false);
});
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should not be empty', () => {
form.setInputValue('rollupPageSize', '');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Page size is required.');
});
it('should be greater than 0', () => {
form.setInputValue('rollupPageSize', '-1');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Page size must be greater than zero.');
});
});
describe('delay', () => {
beforeEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(false);
});
afterEach(() => {
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should validate the interval format', () => {
form.setInputValue('rollupDelay', 'abc');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain('Invalid delay format.');
});
it('should validate the calendar format', () => {
form.setInputValue('rollupDelay', '3y');
userActions.clickNextStep();
expect(getFormErrorsMessages()).toContain(`The 'y' unit only allows values of 1. Try 1y.`);
});
});
});
});

View file

@ -0,0 +1,246 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import { initTestBed, mockServerResponses } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: () => '/api/rollup',
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 5: Metrics', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
let goToStep;
let getMetadataFromEuiTable;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getEuiStepsHorizontalActive,
goToStep,
getMetadataFromEuiTable,
} = initTestBed());
});
afterEach(() => {
server.restore();
});
const numericFields = ['a-numericField', 'c-numericField'];
const dateFields = ['b-dateField', 'd-dateField'];
const goToStepAndOpenFieldChooser = async () => {
await goToStep(5);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
};
describe('layout', () => {
beforeEach(async () => {
await goToStep(5);
});
it('should have the horizontal step active on "Metrics"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Metrics');
});
it('should have the title set to "Metrics"', () => {
expect(testSubjectExists('rollupJobCreateMetricsTitle')).toBe(true);
});
it('should have a link to the documentation', () => {
expect(testSubjectExists('rollupJobCreateMetricsDocsButton')).toBe(true);
});
it('should have the "next" and "back" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(true);
expect(testSubjectExists('rollupJobNextButton')).toBe(true);
expect(testSubjectExists('rollupJobSaveButton')).toBe(false);
});
it('should go to the "Histogram" step when clicking the back button', async () => {
userActions.clickPreviousStep();
expect(getEuiStepsHorizontalActive()).toContain('Histogram');
});
it('should go to the "Review" step when clicking the next button', async () => {
userActions.clickNextStep();
expect(getEuiStepsHorizontalActive()).toContain('Review');
});
it('should have a button to display the list of metrics fields to chose from', () => {
expect(testSubjectExists('rollupJobMetricsFieldChooser')).toBe(false);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
expect(testSubjectExists('rollupJobMetricsFieldChooser')).toBe(true);
});
});
describe('metrics field chooser (flyout)', () => {
describe('layout', () => {
beforeEach(async () => {
await goToStepAndOpenFieldChooser();
});
it('should have the title set to "Add metrics fields"', async () => {
expect(findTestSubject('rollupJobCreateFlyoutTitle').text()).toEqual('Add metrics fields');
});
it('should have a button to close the flyout', () => {
expect(testSubjectExists('rollupJobMetricsFieldChooser')).toBe(true);
findTestSubject('euiFlyoutCloseButton').simulate('click');
expect(testSubjectExists('rollupJobMetricsFieldChooser')).toBe(false);
});
});
describe('table', () => {
beforeEach(async () => {
mockIndexPatternValidityResponse({ numericFields, dateFields });
await goToStepAndOpenFieldChooser();
});
it('should display the fields with metrics and its type', async () => {
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobMetricsFieldChooser-table');
expect(tableCellsValues).toEqual([
['a-numericField', 'numeric'],
['b-dateField', 'date'],
['c-numericField', 'numeric'],
['d-dateField', 'date'],
]);
});
it('should add metric field to the field list when clicking on a row', () => {
let { tableCellsValues } = getMetadataFromEuiTable('rollupJobMetricsFieldList');
expect(tableCellsValues).toEqual([['No metrics fields added']]); // make sure the field list is empty
const { rows } = getMetadataFromEuiTable('rollupJobMetricsFieldChooser-table');
rows[0].reactWrapper.simulate('click'); // Select first row in field chooser
({ tableCellsValues } = getMetadataFromEuiTable('rollupJobMetricsFieldList'));
const [firstRow] = tableCellsValues;
const [field, type] = firstRow;
expect(field).toEqual(rows[0].columns[0].value);
expect(type).toEqual(rows[0].columns[1].value);
});
});
});
describe('fields list', () => {
const addFieldToList = (type = 'numeric') => {
if(!testSubjectExists('rollupJobMetricsFieldChooser-table')) {
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
}
const { rows } = getMetadataFromEuiTable('rollupJobMetricsFieldChooser-table');
for (let i = 0; i < rows.length; i++) {
if (rows[i].columns[1].value === type) {
rows[i].reactWrapper.simulate('click');
break;
}
}
};
it('should have an empty field list', async () => {
await goToStep(5);
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobMetricsFieldList');
expect(tableCellsValues).toEqual([['No metrics fields added']]);
});
describe('when fields are added', () => {
beforeEach(async () => {
mockIndexPatternValidityResponse({ numericFields, dateFields });
await goToStepAndOpenFieldChooser();
});
it('should have "avg", "max", "min", "sum" & "value count" metrics for *numeric* fields', () => {
const numericTypeMetrics = ['avg', 'max', 'min', 'sum', 'value_count'];
addFieldToList('numeric');
numericTypeMetrics.forEach(type => {
try {
expect(testSubjectExists(`rollupJobMetricsCheckbox-${type}`)).toBe(true);
} catch(e) {
throw(new Error(`Test subject "rollupJobMetricsCheckbox-${type}" was not found.`));
}
});
// Make sure there are no other checkboxes
const { rows: [firstRow] } = getMetadataFromEuiTable('rollupJobMetricsFieldList');
const columnWithMetricsCheckboxes = 2;
const metricsCheckboxes = firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find('input');
expect(metricsCheckboxes.length).toBe(numericTypeMetrics.length);
});
it('should have "max", "min", & "value count" metrics for *date* fields', () => {
const dateTypeMetrics = ['max', 'min', 'value_count'];
addFieldToList('date');
dateTypeMetrics.forEach(type => {
try {
expect(testSubjectExists(`rollupJobMetricsCheckbox-${type}`)).toBe(true);
} catch(e) {
throw(new Error(`Test subject "rollupJobMetricsCheckbox-${type}" was not found.`));
}
});
// Make sure there are no other checkboxes
const { rows: [firstRow] } = getMetadataFromEuiTable('rollupJobMetricsFieldList');
const columnWithMetricsCheckboxes = 2;
const metricsCheckboxes = firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find('input');
expect(metricsCheckboxes.length).toBe(dateTypeMetrics.length);
});
it('should not allow to go to the next step if at least one metric type is not selected', () => {
expect(testSubjectExists('rollupJobCreateStepError')).toBeFalsy();
addFieldToList('numeric');
userActions.clickNextStep();
const stepError = findTestSubject('rollupJobCreateStepError');
expect(stepError.length).toBeTruthy();
expect(stepError.text()).toEqual('Select metrics types for these fields or remove them: a-numericField.');
expect(findTestSubject('rollupJobNextButton').props().disabled).toBe(true);
});
it('should have a delete button on each row to remove the metric field', async () => {
const { rows: fieldChooserRows } = getMetadataFromEuiTable('rollupJobMetricsFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click'); // select first item
// Make sure rows value has been set
let { rows: fieldListRows } = getMetadataFromEuiTable('rollupJobMetricsFieldList');
expect(fieldListRows[0].columns[0].value).not.toEqual('No metrics fields added');
const columnsFirstRow = fieldListRows[0].columns;
// The last column is the eui "actions" column
const deleteButton = columnsFirstRow[columnsFirstRow.length - 1].reactWrapper.find('button');
deleteButton.simulate('click');
({ rows: fieldListRows } = getMetadataFromEuiTable('rollupJobMetricsFieldList'));
expect(fieldListRows[0].columns[0].value).toEqual('No metrics fields added');
});
});
});
});

View file

@ -0,0 +1,144 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import { initTestBed, mockServerResponses, nextTick } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: (path) => path,
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 5: Metrics', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
let goToStep;
let getMetadataFromEuiTable;
let form;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getEuiStepsHorizontalActive,
goToStep,
getMetadataFromEuiTable,
form
} = initTestBed());
});
afterEach(() => {
server.restore();
});
describe('layout', () => {
beforeEach(async () => {
await goToStep(6);
});
it('should have the horizontal step active on "Review"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Review');
});
it('should have the title set to "Review"', () => {
expect(testSubjectExists('rollupJobCreateReviewTitle')).toBe(true);
});
it('should have the "next" and "save" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(true);
expect(testSubjectExists('rollupJobNextButton')).toBe(false);
expect(testSubjectExists('rollupJobSaveButton')).toBe(true);
});
it('should go to the "Metrics" step when clicking the back button', async () => {
userActions.clickPreviousStep();
expect(getEuiStepsHorizontalActive()).toContain('Metrics');
});
});
describe('tabs', () => {
const getTabsText = () => findTestSubject('stepReviewTab').map(tab => tab.text());
const selectFirstField = (step) => {
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
// Select the first term field
getMetadataFromEuiTable(`rollupJob${step}FieldChooser-table`)
.rows[0]
.reactWrapper
.simulate('click');
};
it('should have a "Summary" & "JSON" tabs to review the Job', async () => {
await goToStep(6);
expect(getTabsText()).toEqual(['Summary', 'JSON']);
});
it('should have a "Summary", "Terms" & "JSON" tab if a term aggregation was added', async () => {
mockIndexPatternValidityResponse({ numericFields: ['my-field'] });
await goToStep(3);
selectFirstField('Terms');
userActions.clickNextStep(); // go to step 4
userActions.clickNextStep(); // go to step 5
userActions.clickNextStep(); // go to review
expect(getTabsText()).toEqual(['Summary', 'Terms', 'JSON']);
});
it('should have a "Summary", "Histogram" & "JSON" tab if a histogram field was added', async () => {
mockIndexPatternValidityResponse({ numericFields: ['a-field'] });
await goToStep(4);
selectFirstField('Histogram');
form.setInputValue('rollupJobCreateHistogramInterval', 3); // set an interval
userActions.clickNextStep(); // go to step 5
userActions.clickNextStep(); // go to review
expect(getTabsText()).toEqual(['Summary', 'Histogram', 'JSON']);
});
it('should have a "Summary", "Metrics" & "JSON" tab if a histogram field was added', async () => {
mockIndexPatternValidityResponse({ numericFields: ['a-field'], dateFields: ['b-field'] });
await goToStep(5);
selectFirstField('Metrics');
form.selectCheckBox('rollupJobMetricsCheckbox-avg'); // select a metric
userActions.clickNextStep(); // go to review
expect(getTabsText()).toEqual(['Summary', 'Metrics', 'JSON']);
});
});
describe('save()', () => {
it('should call the "create" Api server endpoint', async () => {
await goToStep(6);
const jobCreateApiPath = '/api/rollup/create';
expect(server.requests.find(r => r.url === jobCreateApiPath)).toBe(undefined); // make sure it hasn't been called
userActions.clickSave();
await nextTick();
expect(server.requests.find(r => r.url === jobCreateApiPath)).not.toBe(undefined); // It has been called!
});
});
});

View file

@ -0,0 +1,191 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import sinon from 'sinon';
import { initTestBed, mockServerResponses } from './job_create.test_helpers';
jest.mock('ui/index_patterns', () => {
const { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } = require.requireActual('../../../../../src/legacy/ui/public/index_patterns/constants'); // eslint-disable-line max-len
return { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE };
});
jest.mock('ui/chrome', () => ({
addBasePath: () => '/api/rollup',
breadcrumbs: { set: () => {} },
}));
jest.mock('lodash/function/debounce', () => fn => fn);
describe('Create Rollup Job, step 3: Terms', () => {
let server;
let findTestSubject;
let testSubjectExists;
let userActions;
let mockIndexPatternValidityResponse;
let getEuiStepsHorizontalActive;
let goToStep;
let getMetadataFromEuiTable;
beforeEach(() => {
server = sinon.fakeServer.create();
server.respondImmediately = true;
({ mockIndexPatternValidityResponse } = mockServerResponses(server));
({
findTestSubject,
testSubjectExists,
userActions,
getEuiStepsHorizontalActive,
goToStep,
getMetadataFromEuiTable,
} = initTestBed());
});
afterEach(() => {
server.restore();
});
const numericFields = ['a-numericField', 'c-numericField'];
const keywordFields = ['b-keywordField', 'd-keywordField'];
const goToStepAndOpenFieldChooser = async () => {
await goToStep(3);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
};
describe('layout', () => {
beforeEach(async () => {
await goToStep(3);
});
it('should have the horizontal step active on "Terms"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Terms');
});
it('should have the title set to "Terms"', () => {
expect(testSubjectExists('rollupJobCreateTermsTitle')).toBe(true);
});
it('should have a link to the documentation', () => {
expect(testSubjectExists('rollupJobCreateTermsDocsButton')).toBe(true);
});
it('should have the "next" and "back" button visible', () => {
expect(testSubjectExists('rollupJobBackButton')).toBe(true);
expect(testSubjectExists('rollupJobNextButton')).toBe(true);
expect(testSubjectExists('rollupJobSaveButton')).toBe(false);
});
it('should go to the "Date histogram" step when clicking the back button', async () => {
userActions.clickPreviousStep();
expect(getEuiStepsHorizontalActive()).toContain('Date histogram');
});
it('should go to the "Histogram" step when clicking the next button', async () => {
userActions.clickNextStep();
expect(getEuiStepsHorizontalActive()).toContain('Histogram');
});
it('should have a button to display the list of terms to chose from', () => {
expect(testSubjectExists('rollupJobTermsFieldChooser')).toBe(false);
findTestSubject('rollupJobShowFieldChooserButton').simulate('click');
expect(testSubjectExists('rollupJobTermsFieldChooser')).toBe(true);
});
});
describe('terms field chooser (flyout)', () => {
describe('layout', () => {
beforeEach(async () => {
await goToStepAndOpenFieldChooser();
});
it('should have the title set to "Add terms fields"', async () => {
expect(findTestSubject('rollupJobCreateFlyoutTitle').text()).toEqual('Add terms fields');
});
it('should have a button to close the flyout', () => {
expect(testSubjectExists('rollupJobTermsFieldChooser')).toBe(true);
findTestSubject('euiFlyoutCloseButton').simulate('click');
expect(testSubjectExists('rollupJobTermsFieldChooser')).toBe(false);
});
});
describe('when no terms are available', () => {
it('should indicate it to the user', async () => {
mockIndexPatternValidityResponse({ numericFields: [], keywordFields: [] });
await goToStepAndOpenFieldChooser();
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobTermsFieldChooser-table');
expect(tableCellsValues).toEqual([['No items found']]);
});
});
describe('when terms are available', () => {
beforeEach(async () => {
mockIndexPatternValidityResponse({ numericFields, keywordFields });
await goToStepAndOpenFieldChooser();
});
it('should display the numeric & keyword fields available', async () => {
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobTermsFieldChooser-table');
expect(tableCellsValues).toEqual([
['a-numericField', 'numeric'],
['b-keywordField', 'keyword'],
['c-numericField', 'numeric'],
['d-keywordField', 'keyword'],
]);
});
it('should add term to the field list when clicking on it', () => {
let { tableCellsValues } = getMetadataFromEuiTable('rollupJobTermsFieldList');
expect(tableCellsValues).toEqual([['No terms fields added']]); // make sure the field list is empty
const { rows } = getMetadataFromEuiTable('rollupJobTermsFieldChooser-table');
rows[0].reactWrapper.simulate('click'); // Select first row
({ tableCellsValues } = getMetadataFromEuiTable('rollupJobTermsFieldList'));
const [firstRow] = tableCellsValues;
const [term, type] = firstRow;
expect(term).toEqual('a-numericField');
expect(type).toEqual('numeric');
});
});
});
describe('fields list', () => {
it('should have an empty field list', async () => {
await goToStep(3);
const { tableCellsValues } = getMetadataFromEuiTable('rollupJobTermsFieldList');
expect(tableCellsValues).toEqual([['No terms fields added']]);
});
it('should have a delete button on each row to remove a term', async () => {
// First let's add a term to the list
mockIndexPatternValidityResponse({ numericFields, keywordFields });
await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = getMetadataFromEuiTable('rollupJobTermsFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click');
// Make sure rows value has been set
let { rows: fieldListRows } = getMetadataFromEuiTable('rollupJobTermsFieldList');
expect(fieldListRows[0].columns[0].value).not.toEqual('No terms fields added');
const columnsFirstRow = fieldListRows[0].columns;
// The last column is the eui "actions" column
const deleteButton = columnsFirstRow[columnsFirstRow.length - 1].reactWrapper.find('button');
deleteButton.simulate('click');
({ rows: fieldListRows } = getMetadataFromEuiTable('rollupJobTermsFieldList'));
expect(fieldListRows[0].columns[0].value).toEqual('No terms fields added');
});
});
});

View file

@ -6,7 +6,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mountWithIntl } from 'test_utils/enzyme_helpers'; // eslint-disable-line import/no-unresolved
import { mountWithIntl } from '../../../../test_utils/enzyme_helpers';
import { findTestSubject as findTestSubjectHelper } from '@elastic/eui/lib/test';
const registerTestSubjExists = component => (testSubject, count = 1) => findTestSubjectHelper(component, testSubject).length === count;
@ -33,5 +33,64 @@ export const registerTestBed = (Component, defaultProps, store = {}) => (props)
const testSubjectExists = registerTestSubjExists(component);
const findTestSubject = testSubject => findTestSubjectHelper(component, testSubject);
return { component, testSubjectExists, findTestSubject, setProps };
const getFormErrorsMessages = () => {
const errorMessagesWrappers = component.find('.euiFormErrorText');
return errorMessagesWrappers.map(err => err.text());
};
const setInputValue = (inputTestSubject, value, isAsync = false) => {
const formInput = findTestSubject(inputTestSubject);
formInput.simulate('change', { target: { value } });
component.update();
// In some cases, changing an input value triggers an http request to validate
// it. Even by returning immediately the response on the mock server we need
// to wait until the next tick before the DOM updates.
// Setting isAsync to "true" solves that problem.
if (!isAsync) {
return;
}
return new Promise((resolve) => setTimeout(resolve));
};
const selectCheckBox = (checkboxTestSubject) => {
findTestSubject(checkboxTestSubject).simulate('change', { target: { checked: true } });
};
/**
* Helper to parse an EUI table and return its rows and column reactWrapper
*
* @param {ReactWrapper} table enzyme react wrapper of the EuiBasicTable
*/
const getMetadataFromEuiTable = (tableTestSubject) => {
const rows = findTestSubject(tableTestSubject)
.find('tr')
.slice(1) // we remove the first row as it is the table header
.map(row => ({
reactWrapper: row,
columns: row.find('td').map(col => ({
reactWrapper: col,
// We can't access the td value with col.text() because
// eui adds an extra div inside the table > tr > td on mobile => (".euiTableRowCell__mobileHeader")
value: col.find('.euiTableCellContent').text()
}))
}));
// Also output the raw cell values, in the following format: [[td0, td1, td2], [td0, td1, td2]]
const tableCellsValues = rows.map(({ columns }) => columns.map(col => col.value));
return { rows, tableCellsValues };
};
return {
component,
testSubjectExists,
findTestSubject,
setProps,
getFormErrorsMessages,
getMetadataFromEuiTable,
form: {
setInputValue,
selectCheckBox
}
};
};

View file

@ -18,6 +18,7 @@ export const FieldList = ({
onRemoveField,
addButton,
emptyMessage,
dataTestSubj,
}) => {
let message;
@ -72,6 +73,7 @@ export const FieldList = ({
pagination={pagination}
sorting={true}
message={message}
data-test-subj={dataTestSubj}
/>
);
};
@ -82,4 +84,5 @@ FieldList.propTypes = {
onRemoveField: PropTypes.func,
addButton: PropTypes.node,
emptyMessage: PropTypes.node,
dataTestSubj: PropTypes.string,
};

View file

@ -45,66 +45,61 @@ const NavigationUi = ({
);
}
let previousStepButton;
if (hasPreviousStep) {
previousStepButton = (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="arrowLeft"
onClick={goToPreviousStep}
data-test-subj="rollupJobBackButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.backButton.label"
defaultMessage="Back"
/>
</EuiButtonEmpty>
</EuiFlexItem>
);
}
const previousStepButton = (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="arrowLeft"
onClick={goToPreviousStep}
data-test-subj="rollupJobBackButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.backButton.label"
defaultMessage="Back"
/>
</EuiButtonEmpty>
</EuiFlexItem>
);
let nextStepButton;
if (hasNextStep) {
nextStepButton = (
<EuiFlexItem grow={false}>
<EuiButton
iconType="arrowRight"
iconSide="right"
onClick={goToNextStep}
isDisabled={!canGoToNextStep}
fill
data-test-subj="rollupJobNextButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.nextButton.label"
defaultMessage="Next"
/>
</EuiButton>
</EuiFlexItem>
);
} else {
nextStepButton = (
<EuiFlexItem grow={false}>
<EuiButton
color="secondary"
iconType="check"
onClick={save}
fill
data-test-subj="rollupJobSaveButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.saveButton.label"
defaultMessage="Save"
/>
</EuiButton>
</EuiFlexItem>
);
}
const nextStepButton = (
<EuiFlexItem grow={false}>
<EuiButton
iconType="arrowRight"
iconSide="right"
onClick={goToNextStep}
isDisabled={!canGoToNextStep}
fill
data-test-subj="rollupJobNextButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.nextButton.label"
defaultMessage="Next"
/>
</EuiButton>
</EuiFlexItem>
);
const saveButton = (
<EuiFlexItem grow={false}>
<EuiButton
color="secondary"
iconType="check"
onClick={save}
fill
data-test-subj="rollupJobSaveButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.saveButton.label"
defaultMessage="Save"
/>
</EuiButton>
</EuiFlexItem>
);
return (
<EuiFlexGroup justifyContent="flexStart" gutterSize="m">
{previousStepButton}
{nextStepButton}
{hasPreviousStep && previousStepButton}
{hasNextStep && nextStepButton}
{!hasNextStep && saveButton}
</EuiFlexGroup>
);
};

View file

@ -32,6 +32,7 @@ export const CronDaily = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
@ -50,6 +51,7 @@ export const CronDaily = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyDailyHourSelect"
/>
</EuiFlexItem>
@ -66,6 +68,7 @@ export const CronDaily = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyDailyMinuteSelect"
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -352,6 +352,7 @@ export class CronEditor extends Component {
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencySelect"
/>
</EuiFormRow>

View file

@ -28,6 +28,7 @@ export const CronHourly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiSelect
options={minuteOptions}
@ -44,6 +45,7 @@ export const CronHourly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyHourlyMinuteSelect"
/>
</EuiFormRow>
</Fragment>

View file

@ -34,6 +34,7 @@ export const CronMonthly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiSelect
options={dateOptions}
@ -50,6 +51,7 @@ export const CronMonthly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyMonthlyDateSelect"
/>
</EuiFormRow>
@ -61,6 +63,7 @@ export const CronMonthly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
@ -79,6 +82,7 @@ export const CronMonthly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyMonthlyHourSelect"
/>
</EuiFlexItem>
@ -95,6 +99,7 @@ export const CronMonthly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyMonthlyMinuteSelect"
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -34,6 +34,7 @@ export const CronWeekly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiSelect
options={dayOptions}
@ -50,6 +51,7 @@ export const CronWeekly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyWeeklyDaySelect"
/>
</EuiFormRow>
@ -61,6 +63,7 @@ export const CronWeekly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
@ -79,6 +82,7 @@ export const CronWeekly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyWeeklyHourSelect"
/>
</EuiFlexItem>
@ -95,6 +99,7 @@ export const CronWeekly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyWeeklyMinuteSelect"
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -36,6 +36,7 @@ export const CronYearly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiSelect
options={monthOptions}
@ -52,6 +53,7 @@ export const CronYearly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyYearlyMonthSelect"
/>
</EuiFormRow>
@ -63,6 +65,7 @@ export const CronYearly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiSelect
options={dateOptions}
@ -79,6 +82,7 @@ export const CronYearly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyYearlyDateSelect"
/>
</EuiFormRow>
@ -90,6 +94,7 @@ export const CronYearly = ({
/>
)}
fullWidth
data-test-subj="rollupCronFrequencyConfiguration"
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
@ -108,6 +113,7 @@ export const CronYearly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyYearlyHourSelect"
/>
</EuiFlexItem>
@ -124,6 +130,7 @@ export const CronYearly = ({
</strong>
</EuiText>
)}
data-test-subj="rollupJobCreateFrequencyYearlyMinuteSelect"
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -27,10 +27,12 @@ export class FieldChooser extends Component {
onSelectField: PropTypes.func.isRequired,
columns: PropTypes.array.isRequired,
prompt: PropTypes.string,
dataTestSubj: PropTypes.string,
}
static defaultProps = {
prompt: 'Search',
dataTestSubj: 'rollupJobFieldChooser'
}
constructor(props) {
@ -68,6 +70,7 @@ export class FieldChooser extends Component {
selectedFields,
prompt,
onSelectField,
dataTestSubj,
} = this.props;
const { isOpen, searchValue } = this.state;
@ -80,9 +83,7 @@ export class FieldChooser extends Component {
};
};
let flyout;
if (isOpen) {
const renderFlyout = () => {
// Derive the fields which the user can select.
const selectedFieldNames = selectedFields.map(({ name }) => name);
const unselectedFields = fields.filter(({ name }) => {
@ -95,15 +96,20 @@ export class FieldChooser extends Component {
item.type.toLowerCase().includes(normalizedSearchValue);
}) : unselectedFields;
flyout = (
return (
<EuiFlyout
onClose={this.close}
aria-labelledby="fieldChooserFlyoutTitle"
size="m"
maxWidth={400}
data-test-subj={dataTestSubj}
>
<EuiFlyoutHeader>
<EuiTitle size="m" id="fieldChooserFlyoutTitle">
<EuiTitle
size="m"
id="fieldChooserFlyoutTitle"
data-test-subj="rollupJobCreateFlyoutTitle"
>
<h2>{buttonLabel}</h2>
</EuiTitle>
@ -124,21 +130,23 @@ export class FieldChooser extends Component {
columns={columns}
rowProps={getRowProps}
responsive={false}
data-test-subj={`${dataTestSubj}-table`}
/>
</EuiFlyoutBody>
</EuiFlyout>
);
}
};
return (
<Fragment>
<EuiButton
onClick={this.onButtonClick}
data-test-subj="rollupJobShowFieldChooserButton"
>
{buttonLabel}
</EuiButton>
{flyout}
{isOpen ? renderFlyout() : null}
</Fragment>
);
}

View file

@ -26,6 +26,7 @@ export function StepError({ title = (
title={title}
color="danger"
iconType="cross"
data-test-subj="rollupJobCreateStepError"
/>
</Fragment>
);

View file

@ -194,7 +194,7 @@ export class StepDateHistogramUi extends Component {
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateDateHistogramTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepDateHistogramTitle"
@ -211,6 +211,7 @@ export class StepDateHistogramUi extends Component {
href={dateHistogramDetailsUrl}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateDateHistogramDocsButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.stepDateHistogram.readDocsButtonLabel"
@ -271,6 +272,7 @@ export class StepDateHistogramUi extends Component {
value={dateHistogramField}
onChange={e => onFieldsChange({ dateHistogramField: e.target.value })}
fullWidth
data-test-subj="rollupJobCreateDateFieldSelect"
/>
</EuiFormRow>
@ -311,6 +313,7 @@ export class StepDateHistogramUi extends Component {
value={dateHistogramTimeZone}
onChange={e => onFieldsChange({ dateHistogramTimeZone: e.target.value })}
fullWidth
data-test-subj="rollupJobCreateTimeZoneSelect"
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -81,7 +81,7 @@ export class StepHistogramUi extends Component {
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateHistogramTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepHistogramTitle"
@ -109,6 +109,7 @@ export class StepHistogramUi extends Component {
href={histogramDetailsUrl}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateHistogramDocsButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.stepHistogram.readDocsButtonLabel"
@ -137,8 +138,10 @@ export class StepHistogramUi extends Component {
fields={histogramFields}
selectedFields={histogram}
onSelectField={this.onSelectField}
dataTestSubj="rollupJobHistogramFieldChooser"
/>
)}
dataTestSubj="rollupJobHistogramFieldList"
/>
{this.renderInterval()}
@ -211,6 +214,7 @@ export class StepHistogramUi extends Component {
onChange={e => onFieldsChange({ histogramInterval: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorHistogramInterval)}
fullWidth
data-test-subj="rollupJobCreateHistogramInterval"
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -173,6 +173,7 @@ export class StepLogisticsUi extends Component {
onChange={e => onFieldsChange({ rollupCron: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)}
fullWidth
data-test-subj="rollupAdvancedCron"
/>
</EuiFormRow>
@ -207,7 +208,7 @@ export class StepLogisticsUi extends Component {
/>
<EuiText size="s">
<EuiLink onClick={this.showAdvancedCron}>
<EuiLink onClick={this.showAdvancedCron} data-test-subj="rollupShowAdvancedCronLink">
<FormattedMessage
id="xpack.rollupJobs.create.stepLogistics.sectionSchedule.buttonAdvancedLabel"
defaultMessage="Create cron expression"
@ -248,7 +249,7 @@ export class StepLogisticsUi extends Component {
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateLogisticsTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepLogisticsTitle"
@ -276,6 +277,7 @@ export class StepLogisticsUi extends Component {
href={logisticalDetailsUrl}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateLogisticsDocsButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.stepLogistics.readDocsButtonLabel"
@ -455,6 +457,7 @@ export class StepLogisticsUi extends Component {
isInvalid={Boolean(areStepErrorsVisible && errorRollupPageSize)}
fullWidth
min={0}
data-test-subj="rollupPageSize"
/>
</EuiFormRow>
</EuiDescribedFormGroup>
@ -506,6 +509,7 @@ export class StepLogisticsUi extends Component {
onChange={e => onFieldsChange({ rollupDelay: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorRollupDelay)}
fullWidth
data-test-subj="rollupDelay"
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -136,6 +136,7 @@ export class StepMetricsUi extends Component {
>
<EuiCheckbox
id={`${fieldName}-${type}-checkbox`}
data-test-subj={`rollupJobMetricsCheckbox-${type}`}
label={label}
checked={isSelected}
onChange={() => this.setMetric(fieldName, type, !isSelected)}
@ -210,7 +211,7 @@ export class StepMetricsUi extends Component {
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateMetricsTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepMetricsTitle"
@ -239,6 +240,7 @@ export class StepMetricsUi extends Component {
href={metricsDetailsUrl}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateMetricsDocsButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.stepMetrics.readDocsButtonLabel"
@ -267,8 +269,10 @@ export class StepMetricsUi extends Component {
fields={metricsFields}
selectedFields={metrics}
onSelectField={this.onSelectField}
dataTestSubj="rollupJobMetricsFieldChooser"
/>
)}
dataTestSubj="rollupJobMetricsFieldList"
/>
{this.renderErrors()}

View file

@ -80,7 +80,7 @@ export class StepReviewUi extends Component {
<EuiTab
onClick={() => this.selectTab(tab)}
isSelected={isSelected}
data-test-subj={`stepReviewTab${isSelected ? 'Selected' : ''}`}
data-test-subj="stepReviewTab"
key={index}
>
{tabToHumanizedMap[tab]}
@ -109,7 +109,7 @@ export class StepReviewUi extends Component {
return (
<Fragment>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateReviewTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepReviewTitle"

View file

@ -80,7 +80,7 @@ export class StepTermsUi extends Component {
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle>
<EuiTitle data-test-subj="rollupJobCreateTermsTitle">
<h3>
<FormattedMessage
id="xpack.rollupJobs.create.stepTermsTitle"
@ -110,6 +110,7 @@ export class StepTermsUi extends Component {
href={termsDetailsUrl}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateTermsDocsButton"
>
<FormattedMessage
id="xpack.rollupJobs.create.stepTerms.readDocsButtonLabel"
@ -138,8 +139,10 @@ export class StepTermsUi extends Component {
fields={termsFields}
selectedFields={terms}
onSelectField={this.onSelectField}
dataTestSubj="rollupJobTermsFieldChooser"
/>
)}
dataTestSubj="rollupJobTermsFieldList"
/>
</Fragment>
);