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"