Skip to content

Commit

Permalink
Add date histogram step to Create Rollup Job Wizard (#22789)
Browse files Browse the repository at this point in the history
* Condense app teardown logic.
* Fix React form error with null values by substituting empty strings.
* Block progression if the form contains errors.
* Validate index pattern as you type.
  - Add /api/rollup/index_pattern_validity/{indexPattern} API endpoint.
  - Check that the index pattern matches indices, does not match rollup indices, and contains time fields.
* Validate that index pattern and rollup index aren't the same.
* Add validation for date histogram step.
  - Require date field and interval.
  - Parse interval and delay for valid format.
  - Add ParseEsIntervalInvalidFormatError and ParseEsIntervalInvalidCalendarIntervalError to parseEsInterval module.
* Apply maxWidth to detail panel.
* Show success toasts when jobs are deleted.
  • Loading branch information
cjcenizal committed Sep 28, 2018
1 parent 1030b80 commit 504358c
Show file tree
Hide file tree
Showing 30 changed files with 944 additions and 106 deletions.
22 changes: 22 additions & 0 deletions src/ui/public/utils/parse_es_interval/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.
*/

export { parseEsInterval } from './parse_es_interval';
export { ParseEsIntervalInvalidCalendarIntervalError } from './parse_es_interval_invalid_calendar_interval_error';
export { ParseEsIntervalInvalidFormatError } from './parse_es_interval_invalid_format_error';
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/

import { parseEsInterval } from './parse_es_interval';
import { ParseEsIntervalInvalidCalendarIntervalError } from './parse_es_interval_invalid_calendar_interval_error';
import { ParseEsIntervalInvalidFormatError } from './parse_es_interval_invalid_format_error';

describe('parseEsInterval', () => {
it('should correctly parse an interval containing unit and single value', () => {
Expand All @@ -39,16 +41,29 @@ describe('parseEsInterval', () => {
expect(parseEsInterval('7d')).toEqual({ value: 7, unit: 'd', type: 'fixed' });
});

it('should throw an error for intervals containing calendar unit and multiple value', () => {
expect(() => parseEsInterval('4w')).toThrowError();
expect(() => parseEsInterval('12M')).toThrowError();
expect(() => parseEsInterval('10y')).toThrowError();
it('should throw a ParseEsIntervalInvalidCalendarIntervalError for intervals containing calendar unit and multiple value', () => {
const intervals = ['4w', '12M', '10y'];
expect.assertions(intervals.length);

intervals.forEach(interval => {
try {
parseEsInterval(interval);
} catch (error) {
expect(error instanceof ParseEsIntervalInvalidCalendarIntervalError).toBe(true);
}
});
});

it('should throw an error for invalid interval formats', () => {
expect(() => parseEsInterval('1')).toThrowError();
expect(() => parseEsInterval('h')).toThrowError();
expect(() => parseEsInterval('0m')).toThrowError();
expect(() => parseEsInterval('0.5h')).toThrowError();
it('should throw a ParseEsIntervalInvalidFormatError for invalid interval formats', () => {
const intervals = ['1', 'h', '0m', '0.5h'];
expect.assertions(intervals.length);

intervals.forEach(interval => {
try {
parseEsInterval(interval);
} catch (error) {
expect(error instanceof ParseEsIntervalInvalidFormatError).toBe(true);
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/

import dateMath from '@kbn/datemath';

import { ParseEsIntervalInvalidCalendarIntervalError } from './parse_es_interval_invalid_calendar_interval_error';
import { ParseEsIntervalInvalidFormatError } from './parse_es_interval_invalid_format_error';

const ES_INTERVAL_STRING_REGEX = new RegExp(
'^([1-9][0-9]*)\\s*(' + dateMath.units.join('|') + ')$'
);
Expand Down Expand Up @@ -47,15 +51,15 @@ export function parseEsInterval(interval: string): { value: number; unit: string
.match(ES_INTERVAL_STRING_REGEX);

if (!matches) {
throw Error(`Invalid interval format: ${interval}`);
throw new ParseEsIntervalInvalidFormatError(interval);
}

const value = matches && parseFloat(matches[1]);
const unit = matches && matches[2];
const type = unit && dateMath.unitsMap[unit].type;

if (type === 'calendar' && value !== 1) {
throw Error(`Invalid calendar interval: ${interval}, value must be 1`);
throw new ParseEsIntervalInvalidCalendarIntervalError(interval);
}

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.
*/

export class ParseEsIntervalInvalidCalendarIntervalError extends Error {
constructor(public readonly interval: string) {
super(`Invalid calendar interval: ${interval}, value must be 1`);
this.name = 'ParseEsIntervalInvalidCalendarIntervalError';

// captureStackTrace is only available in the V8 engine, so any browser using
// a different JS engine won't have access to this method.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ParseEsIntervalInvalidCalendarIntervalError);
}

// Babel doesn't support traditional `extends` syntax for built-in classes.
// https://babeljs.io/docs/en/caveats/#classes
Object.setPrototypeOf(this, ParseEsIntervalInvalidCalendarIntervalError.prototype);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.
*/

export class ParseEsIntervalInvalidFormatError extends Error {
constructor(public readonly interval: string) {
super(`Invalid interval format: ${interval}`);
this.name = 'ParseEsIntervalInvalidFormatError';

// captureStackTrace is only available in the V8 engine, so any browser using
// a different JS engine won't have access to this method.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ParseEsIntervalInvalidFormatError);
}

// Babel doesn't support traditional `extends` syntax for built-in classes.
// https://babeljs.io/docs/en/caveats/#classes
Object.setPrototypeOf(this, ParseEsIntervalInvalidFormatError.prototype);
}
}
40 changes: 18 additions & 22 deletions x-pack/plugins/rollup/public/crud_app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { management } from 'ui/management';
import routes from 'ui/routes';

import { CRUD_APP_BASE_PATH } from './constants';
import { setHttpClient } from './services';
import { setHttp } from './services';
import { App } from './app';
import template from './main.html';
import { rollupJobsStore } from './store';
Expand All @@ -28,22 +28,6 @@ esSection.register('rollup_jobs', {
url: `#${CRUD_APP_BASE_PATH}`,
});

export const manageAngularLifecycle = ($scope, $route, elem) => {
const lastRoute = $route.current;

const deregister = $scope.$on('$locationChangeSuccess', () => {
const currentRoute = $route.current;
if (lastRoute.$$route.template === currentRoute.$$route.template) {
$route.current = lastRoute;
}
});

$scope.$on('$destroy', () => {
deregister && deregister();
elem && unmountComponentAtNode(elem);
});
};

const renderReact = async (elem) => {
render(
<I18nProvider>
Expand All @@ -61,15 +45,27 @@ routes.when(`${CRUD_APP_BASE_PATH}/:view?`, {
template: template,
controllerAs: 'rollupJobs',
controller: class IndexRollupJobsController {
constructor($scope, $route, $http) {
constructor($scope, $route, $injector) {
// NOTE: We depend upon Angular's $http service because it's decorated with interceptors,
// e.g. to check license status per request.
setHttpClient($http);
setHttp($injector.get('$http'));

$scope.$$postDigest(() => {
const elem = document.getElementById('rollupJobsReactRoot');
renderReact(elem);
manageAngularLifecycle($scope, $route, elem);
const appElement = document.getElementById('rollupJobsReactRoot');
renderReact(appElement);

const lastRoute = $route.current;
const stopListeningForLocationChange = $scope.$on('$locationChangeSuccess', () => {
const currentRoute = $route.current;
if (lastRoute.$$route.template === currentRoute.$$route.template) {
$route.current = lastRoute;
}
});

$scope.$on('$destroy', () => {
stopListeningForLocationChange();
unmountComponentAtNode(appElement);
});
});
}
}
Expand Down
Loading

0 comments on commit 504358c

Please sign in to comment.