From cb61f6ab7df6ae03ec25b3bdfd400a9222005e69 Mon Sep 17 00:00:00 2001
From: CJ Cenizal
Date: Thu, 13 Sep 2018 15:15:27 -0700
Subject: [PATCH] Add Terms, Histogram, Metrics, and Review steps to Rollup Job
Wizard (#22984)
* Move steps into a single steps directory.
* Update deserializeJob method for symmetry with serializeJob method.
* Return dateFields, keywordFields, and numericFields from index_pattern_validity API endpoint.
* Block forward progress from first step if:
- we're waiting for async validation
- index pattern is invalid
* Add FieldList, FieldChooser, and JobDetails components.
---
package.json | 2 +-
x-pack/package.json | 2 +-
.../crud_app/sections/components/index.js | 11 +
.../sections/components/job_details/index.js | 14 +
.../components/job_details/job_details.js | 72 ++++++
.../job_details}/tabs/index.js | 0
.../job_details}/tabs/tab_histogram.js | 12 +-
.../job_details}/tabs/tab_json.js | 0
.../job_details}/tabs/tab_metrics.js | 11 +-
.../job_details/tabs/tab_summary.js | 198 +++++++++++++++
.../job_details}/tabs/tab_terms.js | 6 +-
.../job_status/index.js | 0
.../job_status/job_status.js | 0
.../sections/job_create/job_create.js | 142 +++++++++--
.../steps/components/field_chooser.js | 120 +++++++++
.../steps/components/field_chooser.less | 9 +
.../job_create/steps/components/field_list.js | 71 ++++++
.../components}/index.js | 3 +-
.../sections/job_create/steps/index.js | 12 +
.../step_date_histogram.js | 19 +-
.../job_create/steps/step_histogram.js | 239 ++++++++++++++++++
.../step_logistics.js | 0
.../sections/job_create/steps/step_metrics.js | 236 +++++++++++++++++
.../sections/job_create/steps/step_review.js | 123 +++++++++
.../sections/job_create/steps/step_terms.js | 148 +++++++++++
.../sections/job_create/steps_config/index.js | 42 +--
.../validate_histogram_interval.js | 35 +++
.../job_list/detail_panel/detail_panel.js | 109 ++++----
.../job_list/detail_panel/tabs/tab_summary.js | 176 -------------
.../sections/job_list/job_table/job_table.js | 17 +-
.../crud_app/services/documentation_links.js | 3 +
.../index.js => services/format_fields.js} | 7 +-
.../rollup/public/crud_app/services/index.js | 39 ++-
.../rollup/public/crud_app/services/jobs.js | 75 ++++--
.../rollup/server/routes/api/indices.js | 36 ++-
x-pack/yarn.lock | 6 +-
yarn.lock | 6 +-
37 files changed, 1651 insertions(+), 350 deletions(-)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/components/job_details/index.js
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/components/job_details/job_details.js
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list/detail_panel => components/job_details}/tabs/index.js (100%)
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list/detail_panel => components/job_details}/tabs/tab_histogram.js (81%)
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list/detail_panel => components/job_details}/tabs/tab_json.js (100%)
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list/detail_panel => components/job_details}/tabs/tab_metrics.js (80%)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_summary.js
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list/detail_panel => components/job_details}/tabs/tab_terms.js (88%)
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list => components}/job_status/index.js (100%)
rename x-pack/plugins/rollup/public/crud_app/sections/{job_list => components}/job_status/job_status.js (100%)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.less
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_list.js
rename x-pack/plugins/rollup/public/crud_app/sections/job_create/{step_date_histogram => steps/components}/index.js (72%)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/index.js
rename x-pack/plugins/rollup/public/crud_app/sections/job_create/{step_date_histogram => steps}/step_date_histogram.js (96%)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js
rename x-pack/plugins/rollup/public/crud_app/sections/job_create/{step_logistics => steps}/step_logistics.js (100%)
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js
create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_histogram_interval.js
delete mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js
rename x-pack/plugins/rollup/public/crud_app/{sections/job_create/step_logistics/index.js => services/format_fields.js} (65%)
diff --git a/package.json b/package.json
index b7a6a0035f461f8..357586eecdccd95 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"url": "https://github.com/elastic/kibana.git"
},
"dependencies": {
- "@elastic/eui": "3.7.0",
+ "@elastic/eui": "4.0.0",
"@elastic/filesaver": "1.1.2",
"@elastic/numeral": "2.3.2",
"@elastic/ui-ace": "0.2.3",
diff --git a/x-pack/package.json b/x-pack/package.json
index 0a8682a85707135..0045b86bb09dbb8 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -76,7 +76,7 @@
"yargs": "4.7.1"
},
"dependencies": {
- "@elastic/eui": "3.7.0",
+ "@elastic/eui": "4.0.0",
"@elastic/node-crypto": "0.1.2",
"@elastic/node-phantom-simple": "2.2.4",
"@elastic/numeral": "2.3.2",
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/index.js b/x-pack/plugins/rollup/public/crud_app/sections/components/index.js
index e972bdeae89f6cc..b1f2150b47e650e 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/components/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/index.js
@@ -5,3 +5,14 @@
*/
export { JobActionMenu } from './job_action_menu';
+
+export {
+ JobDetails,
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+ JOB_DETAILS_TAB_JSON,
+} from './job_details';
+
+export { JobStatus } from './job_status';
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/index.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/index.js
new file mode 100644
index 000000000000000..314f49e563f4c0c
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/index.js
@@ -0,0 +1,14 @@
+/*
+ * 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.
+ */
+
+export {
+ JobDetails,
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+ JOB_DETAILS_TAB_JSON,
+} from './job_details';
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/job_details.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/job_details.js
new file mode 100644
index 000000000000000..bbb35b9b3501592
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/job_details.js
@@ -0,0 +1,72 @@
+/*
+ * 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 React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ TabSummary,
+ TabTerms,
+ TabMetrics,
+ TabJson,
+ TabHistogram,
+} from './tabs';
+
+export const JOB_DETAILS_TAB_SUMMARY = 'Summary';
+export const JOB_DETAILS_TAB_TERMS = 'Terms';
+export const JOB_DETAILS_TAB_HISTOGRAM = 'Histogram';
+export const JOB_DETAILS_TAB_METRICS = 'Metrics';
+export const JOB_DETAILS_TAB_JSON = 'JSON';
+
+const JOB_DETAILS_TABS = [
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+ JOB_DETAILS_TAB_JSON,
+];
+
+export const JobDetails = ({
+ tab,
+ job,
+ stats,
+ json,
+}) => {
+ const {
+ metrics,
+ terms,
+ histogram,
+ histogramInterval,
+ } = job;
+
+ const tabToContentMap = {
+ Summary: (
+
+ ),
+ Terms: (
+
+ ),
+ Histogram: (
+
+ ),
+ Metrics: (
+
+ ),
+ JSON: (
+
+ ),
+ };
+
+ return tabToContentMap[tab];
+};
+
+JobDetails.propTypes = {
+ tab: PropTypes.oneOf(JOB_DETAILS_TABS),
+ job: PropTypes.object,
+};
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/index.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/index.js
similarity index 100%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/index.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/index.js
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_histogram.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_histogram.js
similarity index 81%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_histogram.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_histogram.js
index 6af04d50b51141a..b898a8af16cdc05 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_histogram.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_histogram.js
@@ -14,15 +14,13 @@ import {
EuiFlexItem,
} from '@elastic/eui';
-export const TabHistogram = ({ histogram }) => {
- const { interval, fields } = histogram;
-
+export const TabHistogram = ({ histogram, histogramInterval }) => {
// TODO: Render a table if there are more than 20 fields.
- const renderedTerms = fields.map(field => {
+ const renderedTerms = histogram.map(({ name }) => {
return (
-
- {field}
+
+ {name}
);
});
@@ -38,7 +36,7 @@ export const TabHistogram = ({ histogram }) => {
- Interval: {interval}
+ Interval: {histogramInterval}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_json.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_json.js
similarity index 100%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_json.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_json.js
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_metrics.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_metrics.js
similarity index 80%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_metrics.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_metrics.js
index f57ac6290f54075..e24c9113e7c29a4 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_metrics.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_metrics.js
@@ -14,15 +14,10 @@ import {
export const TabMetrics = ({ metrics }) => {
// TODO: Render a table if there are more than 20 metrics.
- const listMetrics = metrics.map(metric => {
- const {
- field,
- metrics: aggTypes,
- } = metric;
-
+ const listMetrics = metrics.map(({ name, types }) => {
return {
- title: field,
- description: aggTypes.join(', '),
+ title: name,
+ description: types.join(', '),
};
});
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_summary.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_summary.js
new file mode 100644
index 000000000000000..aa67ed439e5a7d8
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_summary.js
@@ -0,0 +1,198 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ EuiDescriptionList,
+ EuiDescriptionListTitle,
+ EuiDescriptionListDescription,
+ EuiTitle,
+ EuiSpacer,
+ EuiIconTip,
+} from '@elastic/eui';
+
+import { JobStatus } from '../../job_status';
+
+export class TabSummary extends Component {
+ static propTypes = {
+ job: PropTypes.object.isRequired,
+ stats: PropTypes.object,
+ };
+
+ renderStats() {
+ const { stats } = this.props;
+
+ if (!stats) {
+ return null;
+ }
+
+ const {
+ documentsProcessed,
+ pagesProcessed,
+ rollupsIndexed,
+ triggerCount,
+ status,
+ } = stats;
+
+ return (
+
+
+
+
+ Stats
+
+
+
+
+
+
+
+ Status
+
+
+
+
+
+
+
+
+ Documents processed
+
+
+
+ {documentsProcessed}
+
+
+
+ Pages processed
+
+
+
+ {pagesProcessed}
+
+
+
+ Rollups indexed
+
+
+
+ {rollupsIndexed}
+
+
+
+ Trigger count
+
+
+
+ {triggerCount}
+
+
+
+ );
+ }
+
+ render() {
+ const { job } = this.props;
+
+ const {
+ indexPattern,
+ rollupIndex,
+ rollupCron,
+ dateHistogramInterval,
+ dateHistogramDelay,
+ dateHistogramTimeZone,
+ dateHistogramField,
+ } = job;
+
+ return (
+
+
+ Logistics
+
+
+
+
+
+
+ Index pattern
+
+
+
+ {indexPattern}
+
+
+
+ Rollup index
+
+
+
+ {rollupIndex}
+
+
+
+ Cron{' '}
+
+
+
+
+ {rollupCron}
+
+
+
+
+
+
+ Date histogram
+
+
+
+
+
+
+ Time field
+
+
+
+ {dateHistogramField}
+
+
+
+ Timezone
+
+
+
+ {dateHistogramTimeZone}
+
+
+
+ Delay
+
+
+
+ {dateHistogramDelay || 'None'}
+
+
+
+ Interval{' '}
+
+
+
+
+ {dateHistogramInterval}
+
+
+
+ {this.renderStats()}
+
+ );
+ }
+}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_terms.js
similarity index 88%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_terms.js
index 5dc016848a76cc4..0695a66b423ce98 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_details/tabs/tab_terms.js
@@ -15,10 +15,10 @@ import {
export const TabTerms = ({ terms }) => {
// TODO: Render a table if there are more than 20 fields.
- const renderedTerms = terms.fields.map(field => {
+ const renderedTerms = terms.map(({ name }) => {
return (
-
- {field}
+
+ {name}
);
});
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/index.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_status/index.js
similarity index 100%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/index.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_status/index.js
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/job_status.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_status/job_status.js
similarity index 100%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/job_status.js
rename to x-pack/plugins/rollup/public/crud_app/sections/components/job_status/job_status.js
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
index 51c257d568b31e5..710bc4c73aa661a 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js
@@ -29,15 +29,23 @@ import { CRUD_APP_BASE_PATH } from '../../constants';
import {
getRouterLinkProps,
validateIndexPattern,
+ formatFields,
} from '../../services';
import { Navigation } from './navigation';
-import { StepLogistics } from './step_logistics';
-import { StepDateHistogram } from './step_date_histogram';
+import {
+ StepLogistics,
+ StepDateHistogram,
+ StepTerms,
+ StepHistogram,
+ StepMetrics,
+ StepReview,
+} from './steps';
import {
STEP_LOGISTICS,
STEP_DATE_HISTOGRAM,
- STEP_GROUPS,
+ STEP_TERMS,
+ STEP_HISTOGRAM,
STEP_METRICS,
STEP_REVIEW,
stepIds,
@@ -47,7 +55,8 @@ import {
const stepIdToTitleMap = {
[STEP_LOGISTICS]: 'Logistics',
[STEP_DATE_HISTOGRAM]: 'Date histogram',
- [STEP_GROUPS]: 'Groups',
+ [STEP_TERMS]: 'Terms',
+ [STEP_HISTOGRAM]: 'Histogram',
[STEP_METRICS]: 'Metrics',
[STEP_REVIEW]: 'Review and save',
};
@@ -75,7 +84,10 @@ export class JobCreateUi extends Component {
stepsFields,
isValidatingIndexPattern: false,
indexPatternAsyncErrors: undefined,
- indexPatternTimeFields: [],
+ indexPatternDateFields: [],
+ indexPatternTermsFields: [],
+ indexPatternHistogramFields: [],
+ indexPatternMetricsFields: [],
};
this.lastIndexPatternValidationTime = 0;
@@ -89,13 +101,15 @@ export class JobCreateUi extends Component {
if (!indexPattern || !indexPattern.trim()) {
this.setState({
indexPatternAsyncErrors: undefined,
- indexPatternTimeFields: [],
+ indexPatternDateFields: [],
isValidatingIndexPattern: false,
});
return;
}
+ // Set the state outside of `requestIndexPatternValidation`, because that function is
+ // debounced.
this.setState({
isValidatingIndexPattern: true,
});
@@ -122,7 +136,9 @@ export class JobCreateUi extends Component {
const {
doesMatchIndices: doesIndexPatternMatchIndices,
doesMatchRollupIndices: doesIndexPatternMatchRollupIndices,
- timeFields: indexPatternTimeFields,
+ dateFields: indexPatternDateFields,
+ numericFields,
+ keywordFields,
} = response.data;
let indexPatternAsyncErrors;
@@ -141,7 +157,7 @@ export class JobCreateUi extends Component {
defaultMessage="Index pattern must match at least one non-rollup index"
/>
)];
- } else if (!indexPatternTimeFields.length) {
+ } else if (!indexPatternDateFields.length) {
indexPatternAsyncErrors = [(
nameB) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ const indexPatternTermsFields = [
+ ...formattedNumericFields,
+ ...formattedKeywordFields,
+ ].sort(sortFields);
+
+ const indexPatternHistogramFields = [ ...formattedNumericFields ].sort(sortFields);
+ const indexPatternMetricsFields = [ ...formattedNumericFields ].sort(sortFields);
+
this.setState({
indexPatternAsyncErrors,
- indexPatternTimeFields,
+ indexPatternDateFields,
+ indexPatternTermsFields,
+ indexPatternHistogramFields,
+ indexPatternMetricsFields,
isValidatingIndexPattern: false,
});
// Select first time field by default.
this.onFieldsChange({
- dateHistogramField: indexPatternTimeFields.length ? indexPatternTimeFields[0] : null,
+ dateHistogramField: indexPatternDateFields.length ? indexPatternDateFields[0] : null,
}, STEP_DATE_HISTOGRAM);
}).catch(() => {
// Ignore all responses except that to the most recent request.
@@ -234,7 +279,18 @@ export class JobCreateUi extends Component {
}
hasStepErrors(stepId) {
- const stepFieldErrors = this.state.stepsFieldErrors[stepId];
+ const {
+ indexPatternAsyncErrors,
+ stepsFieldErrors,
+ } = this.state;
+
+ if (stepId === STEP_LOGISTICS) {
+ if (Boolean(indexPatternAsyncErrors)) {
+ return true;
+ }
+ }
+
+ const stepFieldErrors = stepsFieldErrors[stepId];
return Object.values(stepFieldErrors).some(error => error != null);
}
@@ -283,8 +339,10 @@ export class JobCreateUi extends Component {
dateHistogramTimeZone,
dateHistogramField,
},
- [STEP_GROUPS]: {
+ [STEP_TERMS]: {
terms,
+ },
+ [STEP_HISTOGRAM]: {
histogram,
histogramInterval,
},
@@ -417,8 +475,11 @@ export class JobCreateUi extends Component {
stepsFieldErrors,
areStepErrorsVisible,
isValidatingIndexPattern,
- indexPatternTimeFields,
+ indexPatternDateFields,
indexPatternAsyncErrors,
+ indexPatternTermsFields,
+ indexPatternHistogramFields,
+ indexPatternMetricsFields,
} = this.state;
const currentStepFields = stepsFields[currentStepId];
@@ -445,7 +506,43 @@ export class JobCreateUi extends Component {
onFieldsChange={this.onFieldsChange}
fieldErrors={currentStepFieldErrors}
areStepErrorsVisible={areStepErrorsVisible}
- timeFields={indexPatternTimeFields}
+ dateFields={indexPatternDateFields}
+ />
+ );
+
+ case STEP_TERMS:
+ return (
+
+ );
+
+ case STEP_HISTOGRAM:
+ return (
+
+ );
+
+ case STEP_METRICS:
+ return (
+
+ );
+
+ case STEP_REVIEW:
+ return (
+
);
@@ -455,11 +552,22 @@ export class JobCreateUi extends Component {
}
renderNavigation() {
- const { nextStepId, previousStepId, areStepErrorsVisible } = this.state;
+ const {
+ isValidatingIndexPattern,
+ nextStepId,
+ previousStepId,
+ areStepErrorsVisible,
+ } = this.state;
+
const { isSaving } = this.props;
const hasNextStep = nextStepId != null;
- // Users can click the next step button as long as validation hasn't executed.
- const canGoToNextStep = hasNextStep && (!areStepErrorsVisible || this.canGoToStep(nextStepId));
+
+ // Users can click the next step button as long as validation hasn't executed, and as long
+ // as we're not waiting on async validation to complete.
+ const canGoToNextStep =
+ !isValidatingIndexPattern
+ && hasNextStep
+ && (!areStepErrorsVisible || this.canGoToStep(nextStepId));
return (
{
+ this.setState(state => ({
+ isOpen: !state.isOpen,
+ }));
+ };
+
+ close = () => {
+ this.setState({
+ isOpen: false,
+ });
+ };
+
+ onSearch = (e) => {
+ this.setState({
+ searchValue: e.target.value,
+ });
+ };
+
+ render() {
+ const {
+ columns,
+ label,
+ fields,
+ onSelectField,
+ } = this.props;
+
+ const {
+ searchValue,
+ } = this.state;
+
+ const getRowProps = (field) => {
+ return {
+ className: 'rollupFieldChooserTableRow',
+ onClick: () => {
+ onSelectField(field);
+ this.close();
+ },
+ };
+ };
+
+ const button = (
+
+ {label}
+
+ );
+
+ const items = searchValue ? fields.filter(({ name }) => (
+ name.toLowerCase().includes(searchValue.trim().toLowerCase())
+ )) : fields;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.less b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.less
new file mode 100644
index 000000000000000..cc8d67a141acbb8
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.less
@@ -0,0 +1,9 @@
+.rollupFieldChooserContainer {
+ width: 600px;
+ height: 400px;
+ overflow-y: scroll;
+}
+
+.rollupFieldChooserTableRow {
+ cursor: pointer;
+}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_list.js
new file mode 100644
index 000000000000000..d23a92211575129
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_list.js
@@ -0,0 +1,71 @@
+/*
+ * 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 React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ EuiInMemoryTable,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import './field_chooser.less';
+
+export const FieldList = ({
+ columns,
+ fields,
+ onRemoveField,
+}) => {
+ if (!fields.length) {
+ return null;
+ }
+
+ const extendedColumns = columns.concat({
+ name: 'Remove',
+ width: '80px',
+ actions: [{
+ name: 'Remove',
+ isPrimary: true,
+ description: 'Remove this field',
+ icon: 'cross',
+ type: 'icon',
+ color: 'danger',
+ onClick: (field) => onRemoveField(field),
+ }]
+ });
+
+ const search = {
+ box: {
+ incremental: true,
+ },
+ };
+
+ const pagination = {
+ initialPageSize: 200,
+ pageSizeOptions: [20, 100, 200]
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+FieldList.propTypes = {
+ columns: PropTypes.array.isRequired,
+ fields: PropTypes.array.isRequired,
+ onRemoveField: PropTypes.func.isRequired,
+};
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js
similarity index 72%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/index.js
rename to x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js
index 13376d8cf963686..ac3f2cfd3ea8e33 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js
@@ -4,4 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StepDateHistogram } from './step_date_histogram';
+export { FieldChooser } from './field_chooser';
+export { FieldList } from './field_list';
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/index.js
new file mode 100644
index 000000000000000..d8bd3969f0d630e
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/index.js
@@ -0,0 +1,12 @@
+/*
+ * 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.
+ */
+
+export { StepLogistics } from './step_logistics';
+export { StepDateHistogram } from './step_date_histogram';
+export { StepTerms } from './step_terms';
+export { StepHistogram } from './step_histogram';
+export { StepMetrics } from './step_metrics';
+export { StepReview } from './step_review';
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/step_date_histogram.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js
similarity index 96%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/step_date_histogram.js
rename to x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js
index d3798cff22488c1..55e08936d2ff161 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_date_histogram/step_date_histogram.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_date_histogram.js
@@ -36,15 +36,15 @@ export class StepDateHistogramUi extends Component {
onFieldsChange: PropTypes.func.isRequired,
fieldErrors: PropTypes.object.isRequired,
areStepErrorsVisible: PropTypes.bool.isRequired,
- timeFields: PropTypes.array.isRequired,
+ dateFields: PropTypes.array.isRequired,
}
static getDerivedStateFromProps(props) {
- const { timeFields } = props;
+ const { dateFields } = props;
- const dateHistogramFieldOptions = timeFields.map(timeField => ({
- value: timeField,
- text: timeField,
+ const dateHistogramFieldOptions = dateFields.map(dateField => ({
+ value: dateField,
+ text: dateField,
}));
return { dateHistogramFieldOptions };
@@ -54,7 +54,6 @@ export class StepDateHistogramUi extends Component {
super(props);
this.state = {
- isLoadingTimeFields: true,
dateHistogramFieldOptions: [],
};
}
@@ -168,12 +167,14 @@ export class StepDateHistogramUi extends Component {
)}
error={errorDateHistogramField}
isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramField)}
+ fullWidth
>
onFieldsChange({ dateHistogramField: e.target.value })}
+ fullWidth
/>
@@ -196,11 +197,13 @@ export class StepDateHistogramUi extends Component {
)}
+ fullWidth
>
onFieldsChange({ dateHistogramInterval: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramInterval)}
+ fullWidth
/>
@@ -247,11 +250,13 @@ export class StepDateHistogramUi extends Component {
)}
+ fullWidth
>
onFieldsChange({ dateHistogramDelay: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramDelay)}
+ fullWidth
/>
@@ -280,11 +285,13 @@ export class StepDateHistogramUi extends Component {
}}
/>
)}
+ fullWidth
>
onFieldsChange({ dateHistogramTimeZone: e.target.value })}
isInvalid={Boolean(areStepErrorsVisible && errorDateHistogramTimeZone)}
+ fullWidth
/>
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js
new file mode 100644
index 000000000000000..7647a5478ec6715
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js
@@ -0,0 +1,239 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonEmpty,
+ EuiCallOut,
+ EuiFieldNumber,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+
+import {
+ histogramDetailsUrl,
+} from '../../../services';
+
+import {
+ FieldChooser,
+ FieldList,
+} from './components';
+
+export class StepHistogramUi extends Component {
+ static propTypes = {
+ fields: PropTypes.object.isRequired,
+ onFieldsChange: PropTypes.func.isRequired,
+ histogramFields: PropTypes.array.isRequired,
+ }
+
+ onSelectField = (field) => {
+ const {
+ fields: { histogram },
+ onFieldsChange,
+ } = this.props;
+
+ onFieldsChange({ histogram: histogram.concat(field) });
+ };
+
+ onRemoveField = (field) => {
+ const {
+ fields: { histogram },
+ onFieldsChange,
+ } = this.props;
+
+ onFieldsChange({ histogram: histogram.filter(histogramField => histogramField !== field) });
+ };
+
+ render() {
+ const {
+ fields,
+ histogramFields,
+ } = this.props;
+
+ const {
+ histogram,
+ } = fields;
+
+ const columns = [{
+ field: 'name',
+ name: 'Name',
+ truncateText: true,
+ sortable: true,
+ }];
+
+ const unselectedHistogramFields = histogramFields.filter(histogramField => {
+ return !fields.histogram.includes(histogramField);
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ fields={unselectedHistogramFields}
+ onSelectField={this.onSelectField}
+ />
+
+ {this.renderInterval()}
+
+ {this.renderErrors()}
+
+ );
+ }
+
+ renderInterval() {
+ const {
+ fields,
+ onFieldsChange,
+ areStepErrorsVisible,
+ fieldErrors,
+ } = this.props;
+
+ const {
+ histogram,
+ histogramInterval,
+ } = fields;
+
+ const {
+ histogramInterval: errorHistogramInterval,
+ } = fieldErrors;
+
+ if (!histogram.length) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ error={errorHistogramInterval}
+ isInvalid={Boolean(areStepErrorsVisible && errorHistogramInterval)}
+ >
+ onFieldsChange({ histogramInterval: e.target.value })}
+ isInvalid={Boolean(areStepErrorsVisible && errorHistogramInterval)}
+ />
+
+
+ );
+ }
+
+ renderErrors = () => {
+ const { areStepErrorsVisible } = this.props;
+
+ if (!areStepErrorsVisible) {
+ return null;
+ }
+
+ return (
+
+
+
+ )}
+ color="danger"
+ iconType="cross"
+ />
+
+ );
+ }
+}
+
+export const StepHistogram = injectI18n(StepHistogramUi);
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_logistics/step_logistics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js
similarity index 100%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_create/step_logistics/step_logistics.js
rename to x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js
new file mode 100644
index 000000000000000..1ab761f66be9b6e
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js
@@ -0,0 +1,236 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonEmpty,
+ EuiCheckbox,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+
+import {
+ metricsDetailsUrl,
+} from '../../../services';
+
+import {
+ FieldChooser,
+ FieldList,
+} from './components';
+
+export class StepMetricsUi extends Component {
+ static propTypes = {
+ fields: PropTypes.object.isRequired,
+ onFieldsChange: PropTypes.func.isRequired,
+ metricsFields: PropTypes.array.isRequired,
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.chooserColumns = [{
+ field: 'name',
+ name: 'Name',
+ truncateText: true,
+ sortable: true,
+ }];
+
+ const metricTypesConfig = [{
+ type: 'min',
+ name: '',
+ label: (
+
+ ),
+ }, {
+ type: 'max',
+ name: '',
+ label: (
+
+ ),
+ }, {
+ type: 'sum',
+ name: '',
+ label: (
+
+ ),
+ }, {
+ type: 'avg',
+ name: '',
+ label: (
+
+ ),
+ }, {
+ type: 'value_count',
+ name: '',
+ label: (
+
+ ),
+ }];
+
+ const metricTypeColumns = metricTypesConfig.map(({ type, name, label }) => ({
+ name,
+ render: ({ name: fieldName, types }) => {
+ const isSelected = types.includes(type);
+
+ return (
+ this.setMetric(fieldName, type, !isSelected)}
+ />
+ );
+ },
+ }));
+
+ this.listColumns = this.chooserColumns.concat(metricTypeColumns);
+ }
+
+ onSelectField = (field) => {
+ const {
+ fields: { metrics },
+ onFieldsChange,
+ } = this.props;
+
+ const newMetrics = metrics.concat({
+ name: field.name,
+ types: [],
+ });
+
+ onFieldsChange({ metrics: newMetrics });
+ };
+
+ onRemoveField = (field) => {
+ const {
+ fields: { metrics },
+ onFieldsChange,
+ } = this.props;
+
+ const newMetrics = metrics.filter(({ name }) => name !== field.name);
+
+ onFieldsChange({ metrics: newMetrics });
+ };
+
+ setMetric = (fieldName, metricType, isSelected) => {
+ const {
+ fields: { metrics },
+ onFieldsChange,
+ } = this.props;
+
+ const newMetrics = [ ...metrics ];
+ const { types: updatedTypes } = newMetrics.find(({ name }) => name === fieldName);
+
+ if (isSelected) {
+ updatedTypes.push(metricType);
+ } else {
+ updatedTypes.splice(updatedTypes.indexOf(metricType), 1);
+ }
+
+ onFieldsChange({ metrics: newMetrics });
+ };
+
+ render() {
+ const {
+ fields,
+ metricsFields,
+ } = this.props;
+
+ const {
+ metrics,
+ } = fields;
+
+ const unselectedMetricsFields = metricsFields.filter(metricField => {
+ return !metrics.find(({ name }) => name === metricField.name);
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ fields={unselectedMetricsFields}
+ onSelectField={this.onSelectField}
+ />
+
+ );
+ }
+}
+
+export const StepMetrics = injectI18n(StepMetricsUi);
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js
new file mode 100644
index 000000000000000..f0eb9939ef7c627
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js
@@ -0,0 +1,123 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiSpacer,
+ EuiTab,
+ EuiTabs,
+ EuiTitle,
+} from '@elastic/eui';
+
+import {
+ JobDetails,
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+} from '../../components';
+
+const JOB_DETAILS_TABS = [
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+];
+
+export class StepReviewUi extends Component {
+ static propTypes = {
+ job: PropTypes.object.isRequired,
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ selectedTab: JOB_DETAILS_TABS[0],
+ };
+ }
+
+ selectTab = tab => {
+ this.setState({
+ selectedTab: tab,
+ });
+ };
+
+ renderTabs() {
+ const { selectedTab } = this.state;
+ const { job } = this.props;
+
+ const renderedTabs = [];
+
+ JOB_DETAILS_TABS.forEach((tab, index) => {
+ if (tab === JOB_DETAILS_TAB_TERMS && !job.terms.length) {
+ return;
+ }
+
+ if (tab === JOB_DETAILS_TAB_HISTOGRAM && !job.histogram.length) {
+ return;
+ }
+
+ if (tab === JOB_DETAILS_TAB_METRICS && !job.metrics.length) {
+ return;
+ }
+
+ const isSelected = tab === selectedTab;
+
+ renderedTabs.push(
+ this.selectTab(tab)}
+ isSelected={isSelected}
+ data-test-subj={`stepReviewTab${isSelected ? 'Selected' : ''}`}
+ key={index}
+ >
+ {tab}
+
+ );
+ });
+
+ if (!renderedTabs.length === 1) {
+ return null;
+ }
+
+ return (
+
+
+ {renderedTabs}
+
+
+
+ );
+ }
+
+ render() {
+ const { job } = this.props;
+ const { selectedTab } = this.state;
+
+ return (
+
+
+
+
+
+
+
+ {this.renderTabs()}
+
+
+
+ );
+ }
+}
+
+export const StepReview = injectI18n(StepReviewUi);
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js
new file mode 100644
index 000000000000000..1a2c22daed9a320
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js
@@ -0,0 +1,148 @@
+/*
+ * 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 React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+
+import {
+ termsDetailsUrl,
+} from '../../../services';
+
+import {
+ FieldChooser,
+ FieldList,
+} from './components';
+
+export class StepTermsUi extends Component {
+ static propTypes = {
+ fields: PropTypes.object.isRequired,
+ onFieldsChange: PropTypes.func.isRequired,
+ termsFields: PropTypes.array.isRequired,
+ }
+
+ onSelectField = (field) => {
+ const {
+ fields: { terms },
+ onFieldsChange,
+ } = this.props;
+
+ onFieldsChange({ terms: terms.concat(field) });
+ };
+
+ onRemoveField = (field) => {
+ const {
+ fields: { terms },
+ onFieldsChange,
+ } = this.props;
+
+ onFieldsChange({ terms: terms.filter(term => term !== field) });
+ };
+
+ render() {
+ const {
+ fields,
+ termsFields,
+ } = this.props;
+
+ const {
+ terms,
+ } = fields;
+
+ const unselectedTermsFields = termsFields.filter(termField => {
+ return !fields.terms.includes(termField);
+ });
+
+ const columns = [{
+ field: 'name',
+ name: 'Name',
+ truncateText: true,
+ sortable: true,
+ }, {
+ field: 'type',
+ name: 'Type',
+ truncateText: true,
+ sortable: true,
+ width: '180px',
+ }];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ fields={unselectedTermsFields}
+ onSelectField={this.onSelectField}
+ />
+
+ );
+ }
+}
+
+export const StepTerms = injectI18n(StepTermsUi);
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
index dea0c5aca04a4bd..b678390eab905b0 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js
@@ -12,17 +12,20 @@ import { validateRollupPageSize } from './validate_rollup_page_size';
import { validateDateHistogramField } from './validate_date_histogram_field';
import { validateDateHistogramInterval } from './validate_date_histogram_interval';
import { validateDateHistogramDelay } from './validate_date_histogram_delay';
+import { validateHistogramInterval } from './validate_histogram_interval';
export const STEP_LOGISTICS = 'STEP_LOGISTICS';
export const STEP_DATE_HISTOGRAM = 'STEP_DATE_HISTOGRAM';
-export const STEP_GROUPS = 'STEP_GROUPS';
+export const STEP_TERMS = 'STEP_TERMS';
+export const STEP_HISTOGRAM = 'STEP_HISTOGRAM';
export const STEP_METRICS = 'STEP_METRICS';
export const STEP_REVIEW = 'STEP_REVIEW';
export const stepIds = [
STEP_LOGISTICS,
STEP_DATE_HISTOGRAM,
- STEP_GROUPS,
+ STEP_TERMS,
+ STEP_HISTOGRAM,
STEP_METRICS,
STEP_REVIEW,
];
@@ -79,24 +82,33 @@ export const stepIdToStepConfigMap = {
return errors;
},
},
- [STEP_GROUPS]: {
+ [STEP_TERMS]: {
defaultFields: {
- terms: ['index.keyword'],
- histogram: ['memory'],
- histogramInterval: 5,
+ terms: [],
},
},
- [STEP_METRICS]: {
+ [STEP_HISTOGRAM]: {
defaultFields: {
- metrics: [{
- 'field': 'bytes',
- 'metrics': ['min', 'max', 'avg']
- }, {
- 'field': 'memory',
- 'metrics': ['min', 'max', 'avg']
- }],
+ histogram: [],
+ histogramInterval: undefined,
+ },
+ fieldsValidator: fields => {
+ const {
+ histogram,
+ histogramInterval,
+ } = fields;
+
+ const errors = {
+ histogramInterval: validateHistogramInterval(histogram, histogramInterval),
+ };
+
+ return errors;
},
},
- [STEP_REVIEW]: {
+ [STEP_METRICS]: {
+ defaultFields: {
+ metrics: [],
+ },
},
+ [STEP_REVIEW]: {},
};
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_histogram_interval.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_histogram_interval.js
new file mode 100644
index 000000000000000..6d6b027eab8f011
--- /dev/null
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_histogram_interval.js
@@ -0,0 +1,35 @@
+/*
+ * 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 React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+export function validateHistogramInterval(histogram, histogramInterval) {
+ // If there are no selected histogram fields then we don't need to validate the interval.
+ if (!histogram.length) {
+ return undefined;
+ }
+
+ if (histogramInterval === 0) {
+ return [(
+
+ )];
+ }
+
+ if (!histogramInterval) {
+ return [(
+
+ )];
+ }
+
+ return undefined;
+}
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
index 8bb658b05c26771..5c9d9c490b88fe7 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js
@@ -26,29 +26,35 @@ import {
} from '@elastic/eui';
import {
- TabSummary,
- TabTerms,
- TabMetrics,
- TabJson,
- TabHistogram,
-} from './tabs';
-
-import { JobActionMenu } from '../../components';
-
-const tabs = ['Summary', 'Terms', 'Histogram', 'Metrics', 'JSON'];
+ JobActionMenu,
+ JobDetails,
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+ JOB_DETAILS_TAB_JSON,
+} from '../../components';
+
+const JOB_DETAILS_TABS = [
+ JOB_DETAILS_TAB_SUMMARY,
+ JOB_DETAILS_TAB_TERMS,
+ JOB_DETAILS_TAB_HISTOGRAM,
+ JOB_DETAILS_TAB_METRICS,
+ JOB_DETAILS_TAB_JSON,
+];
export class DetailPanelUi extends Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
job: PropTypes.object,
- panelType: PropTypes.oneOf(tabs),
+ panelType: PropTypes.oneOf(JOB_DETAILS_TABS),
closeDetailPanel: PropTypes.func.isRequired,
openDetailPanel: PropTypes.func.isRequired,
}
static defaultProps = {
- panelType: tabs[0],
+ panelType: JOB_DETAILS_TABS[0],
}
constructor(props) {
@@ -62,23 +68,32 @@ export class DetailPanelUi extends Component {
return;
}
- const renderedTabs = tabs.map((tab, index) => {
- if (tab === 'Terms' && !job.terms.fields.length) {
+ const {
+ id,
+ terms,
+ histogram,
+ metrics,
+ } = job;
+
+ const renderedTabs = [];
+
+ JOB_DETAILS_TABS.map((tab, index) => {
+ if (tab === JOB_DETAILS_TAB_TERMS && !terms.length) {
return;
}
- if (tab === 'Histogram' && !job.histogram.fields.length) {
+ if (tab === JOB_DETAILS_TAB_HISTOGRAM && !histogram.length) {
return;
}
- if (tab === 'Metrics' && !job.metrics.length) {
+ if (tab === JOB_DETAILS_TAB_METRICS && !metrics.length) {
return;
}
const isSelected = tab === panelType;
- return (
+ renderedTabs.push(
openDetailPanel({ panelType: tab, jobId: job.id })}
+ onClick={() => openDetailPanel({ panelType: tab, jobId: id })}
isSelected={isSelected}
data-test-subj={`detailPanelTab${isSelected ? 'Selected' : ''}`}
key={index}
@@ -86,7 +101,7 @@ export class DetailPanelUi extends Component {
{tab}
);
- }).filter(tab => tab);
+ });
return (
@@ -102,17 +117,6 @@ export class DetailPanelUi extends Component {
const { panelType, job, intl } = this.props;
const {
- id,
- indexPattern,
- rollupIndex,
- rollupCron,
- dateHistogramInterval,
- dateHistogramDelay,
- dateHistogramTimeZone,
- dateHistogramField,
- metrics,
- terms,
- histogram,
status,
documentsProcessed,
pagesProcessed,
@@ -121,44 +125,23 @@ export class DetailPanelUi extends Component {
json,
} = job;
- const tabToContentMap = {
- Summary: (
-
- ),
- Terms: (
-
- ),
- Histogram: (
-
- ),
- Metrics: (
-
- ),
- JSON: (
-
- ),
+ const stats = {
+ status,
+ documentsProcessed,
+ pagesProcessed,
+ rollupsIndexed,
+ triggerCount,
};
- const tabContent = tabToContentMap[panelType];
-
return (
- {tabContent}
+
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js
deleted file mode 100644
index abdc376c162e59f..000000000000000
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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 React, { Fragment } from 'react';
-
-import {
- EuiDescriptionList,
- EuiDescriptionListTitle,
- EuiDescriptionListDescription,
- EuiTitle,
- EuiSpacer,
- EuiIconTip,
-} from '@elastic/eui';
-
-import { JobStatus } from '../../job_status';
-
-export const TabSummary = ({
- id,
- indexPattern,
- rollupIndex,
- rollupCron,
- dateHistogramInterval,
- dateHistogramDelay,
- dateHistogramTimeZone,
- dateHistogramField,
- documentsProcessed,
- pagesProcessed,
- rollupsIndexed,
- triggerCount,
- status,
-}) => {
- return (
-
-
- Config
-
-
-
-
-
-
- ID
-
-
-
- {id}
-
-
-
- Index pattern
-
-
-
- {indexPattern}
-
-
-
- Rollup index
-
-
-
- {rollupIndex}
-
-
-
- Cron{' '}
-
-
-
-
- {rollupCron}
-
-
-
-
-
-
- Date histogram
-
-
-
-
-
-
- Time field
-
-
-
- {dateHistogramField}
-
-
-
- Timezone
-
-
-
- {dateHistogramTimeZone}
-
-
-
- Delay
-
-
-
- {dateHistogramDelay || 'None'}
-
-
-
- Interval{' '}
-
-
-
-
- {dateHistogramInterval}
-
-
-
-
-
-
- Stats
-
-
-
-
-
-
- Status
-
-
-
-
-
-
-
- Documents processed
-
-
-
- {documentsProcessed}
-
-
-
- Pages processed
-
-
-
- {pagesProcessed}
-
-
-
- Rollups indexed
-
-
-
- {rollupsIndexed}
-
-
-
- Trigger count
-
-
-
- {triggerCount}
-
-
-
- );
-};
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
index 5e7d900481c7caa..ba7232d4d92e37d 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
+++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js
@@ -28,9 +28,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
-import { JobActionMenu } from '../../components';
-
-import { JobStatus } from '../job_status';
+import { JobActionMenu, JobStatus } from '../../components';
const COLUMNS = [{
name: i18n.translate('xpack.rollupJobs.jobTable.headers.nameHeader', {
@@ -81,11 +79,11 @@ const COLUMNS = [{
const { histogram, terms } = job;
const humanizedGroupNames = [];
- if (histogram) {
+ if (histogram.length) {
humanizedGroupNames.push('histogram');
}
- if (terms.fields.length) {
+ if (terms.length) {
humanizedGroupNames.push('terms');
}
@@ -94,7 +92,7 @@ const COLUMNS = [{
return humanizedGroupNames.join(', ');
}
- return 'None';
+ return '';
},
}, {
name: i18n.translate('xpack.rollupJobs.jobTable.headers.metricsHeader', {
@@ -103,7 +101,12 @@ const COLUMNS = [{
truncateText: true,
render: job => {
const { metrics } = job;
- return metrics.map(metric => metric.field).join(', ');
+
+ if (metrics.length) {
+ return metrics.map(metric => metric.name).join(', ');
+ }
+
+ return '';
},
}];
diff --git a/x-pack/plugins/rollup/public/crud_app/services/documentation_links.js b/x-pack/plugins/rollup/public/crud_app/services/documentation_links.js
index eebaec9ecc28e41..426a7b89779ef92 100644
--- a/x-pack/plugins/rollup/public/crud_app/services/documentation_links.js
+++ b/x-pack/plugins/rollup/public/crud_app/services/documentation_links.js
@@ -10,5 +10,8 @@ const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LIN
export const logisticalDetailsUrl = `${esBase}/rollup-job-config.html#_logistical_details`;
export const dateHistogramDetailsUrl = `${esBase}/rollup-job-config.html#_date_histogram_2`;
+export const termsDetailsUrl = `${esBase}/rollup-job-config.html#_terms_2`;
+export const histogramDetailsUrl = `${esBase}/rollup-job-config.html#_histogram_2`;
+export const metricsDetailsUrl = `${esBase}/rollup-job-config.html#rollup-metrics-config`;
export const dateHistogramAggregationUrl = `${esBase}/search-aggregations-bucket-datehistogram-aggregation.html`;
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_logistics/index.js b/x-pack/plugins/rollup/public/crud_app/services/format_fields.js
similarity index 65%
rename from x-pack/plugins/rollup/public/crud_app/sections/job_create/step_logistics/index.js
rename to x-pack/plugins/rollup/public/crud_app/services/format_fields.js
index 57948d4de7ef50e..96fc82517c58b7b 100644
--- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/step_logistics/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/services/format_fields.js
@@ -4,4 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StepLogistics } from './step_logistics';
+export function formatFields(fieldNames, type) {
+ return fieldNames.map(fieldName => ({
+ name: fieldName,
+ type,
+ }));
+}
diff --git a/x-pack/plugins/rollup/public/crud_app/services/index.js b/x-pack/plugins/rollup/public/crud_app/services/index.js
index 4e90e69313d9b54..319af583b46e3f8 100644
--- a/x-pack/plugins/rollup/public/crud_app/services/index.js
+++ b/x-pack/plugins/rollup/public/crud_app/services/index.js
@@ -13,21 +13,42 @@ export {
validateIndexPattern,
} from './api';
+export {
+ logisticalDetailsUrl,
+ dateHistogramDetailsUrl,
+ dateHistogramAggregationUrl,
+ termsDetailsUrl,
+ histogramDetailsUrl,
+ metricsDetailsUrl,
+} from './documentation_links';
+
+export {
+ filterItems,
+} from './filter_items';
+
+export {
+ flattenPanelTree,
+} from './flatten_panel_tree';
+
+export {
+ formatFields,
+} from './format_fields';
+
export {
setHttp,
getHttp,
} from './http_provider';
-export { sortTable } from './sort_table';
-export { filterItems } from './filter_items';
-export { flattenPanelTree } from './flatten_panel_tree';
-
export {
serializeJob,
deserializeJob,
deserializeJobs,
} from './jobs';
+export {
+ extractQueryParams,
+} from './query_params';
+
export {
registerRouter,
getRouter,
@@ -35,11 +56,5 @@ export {
} from './routing';
export {
- logisticalDetailsUrl,
- dateHistogramDetailsUrl,
- dateHistogramAggregationUrl,
-} from './documentation_links';
-
-export {
- extractQueryParams,
-} from './query_params';
+ sortTable,
+} from './sort_table';
diff --git a/x-pack/plugins/rollup/public/crud_app/services/jobs.js b/x-pack/plugins/rollup/public/crud_app/services/jobs.js
index 0d79886a40f7142..218315e97e6729b 100644
--- a/x-pack/plugins/rollup/public/crud_app/services/jobs.js
+++ b/x-pack/plugins/rollup/public/crud_app/services/jobs.js
@@ -31,7 +31,7 @@ export function serializeJob(jobConfig) {
histogramInterval,
} = jobConfig;
- return {
+ const serializedJob = {
id,
index_pattern: indexPattern,
rollup_index: rollupIndex,
@@ -45,15 +45,36 @@ export function serializeJob(jobConfig) {
time_zone: dateHistogramTimeZone,
field: dateHistogramField,
}),
- terms: {
- fields: terms,
- },
- histogram: {
- interval: histogramInterval,
- fields: histogram,
- },
},
};
+
+ if (terms.length) {
+ serializedJob.terms = {
+ fields: terms.map(({ name }) => name),
+ };
+ }
+
+ if (histogram.length) {
+ serializedJob.histogram = {
+ interval: histogramInterval,
+ fields: histogram.map(({ name }) => name),
+ };
+ }
+
+ if (metrics.length) {
+ serializedJob.metrics = [];
+ metrics.forEach(({ name, types }) => {
+ // Exclude any metrics which have been selected but not configured with any types.
+ if (types.length) {
+ serializedJob.metrics.push({
+ field: name,
+ metrics: types,
+ });
+ }
+ });
+ }
+
+ return serializedJob;
}
export function deserializeJob(job) {
@@ -63,7 +84,7 @@ export function deserializeJob(job) {
index_pattern: indexPattern,
rollup_index: rollupIndex,
cron: rollupCron,
- metrics = [],
+ metrics,
groups: {
date_histogram: {
interval: dateHistogramInterval,
@@ -71,12 +92,8 @@ export function deserializeJob(job) {
time_zone: dateHistogramTimeZone,
field: dateHistogramField,
},
- terms = {
- fields: [],
- },
- histogram = {
- fields: [],
- },
+ terms,
+ histogram,
},
},
status: {
@@ -92,7 +109,7 @@ export function deserializeJob(job) {
const json = job;
- return {
+ const deserializedJob = {
id,
indexPattern,
rollupIndex,
@@ -101,16 +118,36 @@ export function deserializeJob(job) {
dateHistogramDelay,
dateHistogramTimeZone,
dateHistogramField,
- metrics,
- terms,
- histogram,
status,
+ metrics: [],
+ terms: [],
+ histogram: [],
documentsProcessed,
pagesProcessed,
rollupsIndexed,
triggerCount,
json,
};
+
+ if (metrics) {
+ metrics.forEach(({ field, metrics }) => {
+ deserializedJob.metrics.push({
+ name: field,
+ types: metrics,
+ });
+ });
+ }
+
+ if (terms) {
+ deserializedJob.terms = terms.fields.map(name => ({ name }));
+ }
+
+ if (histogram) {
+ deserializedJob.histogram = histogram.fields.map(name => ({ name }));
+ deserializedJob.histogramInterval = histogram.interval;
+ }
+
+ return deserializedJob;
}
export function deserializeJobs(jobs) {
diff --git a/x-pack/plugins/rollup/server/routes/api/indices.js b/x-pack/plugins/rollup/server/routes/api/indices.js
index 794d098665cc0ff..24298bb27b6f09e 100644
--- a/x-pack/plugins/rollup/server/routes/api/indices.js
+++ b/x-pack/plugins/rollup/server/routes/api/indices.js
@@ -9,6 +9,11 @@ import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers';
import { licensePreRoutingFactory } from'../../lib/license_pre_routing_factory';
import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities';
+function isNumericField(fieldCapability) {
+ const numericTypes = ['long', 'integer', 'short', 'byte', 'double', 'float', 'half_float', 'scaled_float'];
+ return numericTypes.some(numericType => fieldCapability[numericType] != null);
+}
+
export function registerIndicesRoute(server) {
const isEsError = isEsErrorFactory(server);
const licensePreRouting = licensePreRoutingFactory(server);
@@ -42,7 +47,7 @@ export function registerIndicesRoute(server) {
* Returns information on validiity of an index pattern for creating a rollup job:
* - Does the index pattern match any indices?
* - Does the index pattern match rollup indices?
- * - Which time fields are available in the matching indices?
+ * - Which date fields, numeric fields, and keyword fields are available in the matching indices?
*/
server.route({
path: '/api/rollup/index_pattern_validity/{indexPattern}',
@@ -63,18 +68,33 @@ export function registerIndicesRoute(server) {
const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0;
const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0;
+ const dateFields = [];
+ const numericFields = [];
+ const keywordFields = [];
+
const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields);
- const timeFields = fieldCapabilitiesEntries.reduce((accumulatedTimeFields, [ fieldName, fieldCapability ]) => {
+ fieldCapabilitiesEntries.forEach(([ fieldName, fieldCapability ]) => {
if (fieldCapability.date) {
- accumulatedTimeFields.push(fieldName);
+ dateFields.push(fieldName);
+ return;
+ }
+
+ if (isNumericField(fieldCapability)) {
+ numericFields.push(fieldName);
+ return;
+ }
+
+ if (fieldCapability.keyword) {
+ keywordFields.push(fieldName);
}
- return accumulatedTimeFields;
- }, []);
+ });
reply({
doesMatchIndices,
doesMatchRollupIndices,
- timeFields,
+ dateFields,
+ numericFields,
+ keywordFields,
});
} catch(err) {
// 404s are still valid results.
@@ -82,7 +102,9 @@ export function registerIndicesRoute(server) {
return reply({
doesMatchIndices: false,
doesMatchRollupIndices: false,
- timeFields: [],
+ dateFields: [],
+ numericFields: [],
+ keywordFields: [],
});
}
diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock
index 686d8b5eb988583..4b2809434590f60 100644
--- a/x-pack/yarn.lock
+++ b/x-pack/yarn.lock
@@ -10,9 +10,9 @@
esutils "^2.0.2"
js-tokens "^3.0.0"
-"@elastic/eui@3.7.0":
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-3.7.0.tgz#276d2a8e724778d15192b3847247d3731c7ff738"
+"@elastic/eui@4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-4.0.0.tgz#4e8d616ec9529a74fc761c3ae2f48fc6222cedd1"
dependencies:
classnames "^2.2.5"
core-js "^2.5.1"
diff --git a/yarn.lock b/yarn.lock
index 800284455806d59..444baf0ac59929b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -81,9 +81,9 @@
version "0.0.0"
uid ""
-"@elastic/eui@3.7.0":
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-3.7.0.tgz#276d2a8e724778d15192b3847247d3731c7ff738"
+"@elastic/eui@4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-4.0.0.tgz#4e8d616ec9529a74fc761c3ae2f48fc6222cedd1"
dependencies:
classnames "^2.2.5"
core-js "^2.5.1"