From df8c24531bcd96c5f7829345d63d83d94655e05a Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 17 Aug 2018 11:07:19 -0700 Subject: [PATCH] Add a simple read-only table of rollup jobs and a details panel (#21900) --- .../rollup/common/constants/crud_app.js | 7 + .../plugins/rollup/common/constants/index.js | 1 + x-pack/plugins/rollup/common/index.js | 0 x-pack/plugins/rollup/index.js | 11 +- x-pack/plugins/rollup/public/crud_app/app.js | 19 ++ .../plugins/rollup/public/crud_app/index.js | 72 +++++ .../plugins/rollup/public/crud_app/main.html | 3 + .../rollup/public/crud_app/sections/index.js | 7 + .../detail_panel/detail_panel.container.js | 42 +++ .../job_list/detail_panel/detail_panel.js | 157 +++++++++++ .../sections/job_list/detail_panel/index.js | 7 + .../job_list/detail_panel/tabs/index.js | 11 + .../detail_panel/tabs/tab_histogram.js | 55 ++++ .../job_list/detail_panel/tabs/tab_json.js | 40 +++ .../job_list/detail_panel/tabs/tab_metrics.js | 40 +++ .../job_list/detail_panel/tabs/tab_summary.js | 176 ++++++++++++ .../job_list/detail_panel/tabs/tab_terms.js | 41 +++ .../crud_app/sections/job_list/index.js | 7 + .../sections/job_list/job_list.container.js | 22 ++ .../crud_app/sections/job_list/job_list.js | 45 +++ .../sections/job_list/job_status/index.js | 7 + .../job_list/job_status/job_status.js | 36 +++ .../sections/job_list/job_table/index.js | 7 + .../job_list/job_table/job_table.container.js | 60 ++++ .../sections/job_list/job_table/job_table.js | 256 ++++++++++++++++++ .../rollup/public/crud_app/services/api.js | 20 ++ .../public/crud_app/services/filter_items.js | 17 ++ .../rollup/public/crud_app/services/index.js | 13 + .../rollup/public/crud_app/services/jobs.js | 66 +++++ .../public/crud_app/services/sort_table.js | 19 ++ .../crud_app/store/actions/detail_panel.js | 14 + .../public/crud_app/store/actions/index.js | 24 ++ .../crud_app/store/actions/load_jobs.js | 21 ++ .../crud_app/store/actions/table_state.js | 22 ++ .../rollup/public/crud_app/store/index.js | 7 + .../crud_app/store/reducers/detail_panel.js | 30 ++ .../public/crud_app/store/reducers/index.js | 16 ++ .../public/crud_app/store/reducers/jobs.js | 35 +++ .../crud_app/store/reducers/table_state.js | 55 ++++ .../public/crud_app/store/selectors/index.js | 97 +++++++ .../rollup/public/crud_app/store/store.js | 21 ++ .../server/client/elasticsearch_rollup.js | 9 + .../plugins/rollup/server/routes/api/index.js | 1 + .../plugins/rollup/server/routes/api/jobs.js | 31 +++ 44 files changed, 1646 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/rollup/common/constants/crud_app.js create mode 100644 x-pack/plugins/rollup/common/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/app.js create mode 100644 x-pack/plugins/rollup/public/crud_app/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/main.html create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_histogram.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_json.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_metrics.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/job_status.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js create mode 100644 x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js create mode 100644 x-pack/plugins/rollup/public/crud_app/services/api.js create mode 100644 x-pack/plugins/rollup/public/crud_app/services/filter_items.js create mode 100644 x-pack/plugins/rollup/public/crud_app/services/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/services/jobs.js create mode 100644 x-pack/plugins/rollup/public/crud_app/services/sort_table.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/actions/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/reducers/detail_panel.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/reducers/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/reducers/table_state.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/selectors/index.js create mode 100644 x-pack/plugins/rollup/public/crud_app/store/store.js create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs.js diff --git a/x-pack/plugins/rollup/common/constants/crud_app.js b/x-pack/plugins/rollup/common/constants/crud_app.js new file mode 100644 index 0000000000000..1813373776f46 --- /dev/null +++ b/x-pack/plugins/rollup/common/constants/crud_app.js @@ -0,0 +1,7 @@ +/* + * 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 const CRUD_APP_BASE_PATH = '/management/elasticsearch/index_rollup_jobs/'; diff --git a/x-pack/plugins/rollup/common/constants/index.js b/x-pack/plugins/rollup/common/constants/index.js index 035454fd7b435..33c80f8230e6d 100644 --- a/x-pack/plugins/rollup/common/constants/index.js +++ b/x-pack/plugins/rollup/common/constants/index.js @@ -5,3 +5,4 @@ */ export { PLUGIN } from './plugin'; +export { CRUD_APP_BASE_PATH } from './crud_app'; diff --git a/x-pack/plugins/rollup/common/index.js b/x-pack/plugins/rollup/common/index.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/rollup/index.js b/x-pack/plugins/rollup/index.js index 2f91455c91288..633a9d3eb6470 100644 --- a/x-pack/plugins/rollup/index.js +++ b/x-pack/plugins/rollup/index.js @@ -7,7 +7,12 @@ import { resolve } from 'path'; import { PLUGIN } from './common/constants'; import { registerLicenseChecker } from './server/lib/register_license_checker'; -import { registerIndicesRoute, registerFieldsForWildcardRoute, registerSearchRoute } from './server/routes/api'; +import { + registerIndicesRoute, + registerFieldsForWildcardRoute, + registerSearchRoute, + registerJobsRoute, +} from './server/routes/api'; export function rollup(kibana) { return new kibana.Plugin({ @@ -15,6 +20,9 @@ export function rollup(kibana) { publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], uiExports: { + managementSections: [ + 'plugins/rollup/crud_app', + ], indexManagement: [ 'plugins/rollup/index_pattern_creation', 'plugins/rollup/index_pattern_list', @@ -31,6 +39,7 @@ export function rollup(kibana) { registerIndicesRoute(server); registerFieldsForWildcardRoute(server); registerSearchRoute(server); + registerJobsRoute(server); } }); } diff --git a/x-pack/plugins/rollup/public/crud_app/app.js b/x-pack/plugins/rollup/public/crud_app/app.js new file mode 100644 index 0000000000000..e6d107643dcd5 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/app.js @@ -0,0 +1,19 @@ +/* + * 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 { Switch, Route } from 'react-router-dom'; +import { CRUD_APP_BASE_PATH } from '../../common/constants'; +import { JobList } from './sections'; + +export const App = () => ( +
+ + + +
+); + diff --git a/x-pack/plugins/rollup/public/crud_app/index.js b/x-pack/plugins/rollup/public/crud_app/index.js new file mode 100644 index 0000000000000..8a93515524f1e --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/index.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 { render, unmountComponentAtNode } from 'react-dom'; +import { Provider } from 'react-redux'; +import { HashRouter } from 'react-router-dom'; +import { management } from 'ui/management'; +import routes from 'ui/routes'; + +import { CRUD_APP_BASE_PATH } from '../../common/constants'; +import { setHttpClient } from './services'; +import { App } from './app'; +import template from './main.html'; +import { rollupJobsStore } from './store'; + +const esSection = management.getSection('elasticsearch'); + +esSection.register('rollup_jobs', { + visible: true, + display: 'Rollup Jobs', + order: 2, + url: `#${CRUD_APP_BASE_PATH}home` +}); + +export const manageAngularLifecycle = ($scope, $route, elem) => { + const lastRoute = $route.current; + + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + if (lastRoute.$$route.template === currentRoute.$$route.template) { + $route.current = lastRoute; + } + }); + + $scope.$on('$destroy', () => { + deregister && deregister(); + elem && unmountComponentAtNode(elem); + }); +}; + +const renderReact = async (elem) => { + render( + + + + + , + elem + ); +}; + +routes.when(`${CRUD_APP_BASE_PATH}:view?`, { + template: template, + controllerAs: 'rollupJobs', + controller: class IndexRollupJobsController { + constructor($scope, $route, $http) { + // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, + // e.g. to check license status per request. + setHttpClient($http); + + $scope.$$postDigest(() => { + const elem = document.getElementById('rollupJobsReactRoot'); + renderReact(elem); + manageAngularLifecycle($scope, $route, elem); + }); + } + } +}); diff --git a/x-pack/plugins/rollup/public/crud_app/main.html b/x-pack/plugins/rollup/public/crud_app/main.html new file mode 100644 index 0000000000000..cc4a6c9bfcbb7 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/main.html @@ -0,0 +1,3 @@ + +
+
diff --git a/x-pack/plugins/rollup/public/crud_app/sections/index.js b/x-pack/plugins/rollup/public/crud_app/sections/index.js new file mode 100644 index 0000000000000..71e6461d27e6f --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/index.js @@ -0,0 +1,7 @@ +/* + * 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 { JobList } from './job_list'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js new file mode 100644 index 0000000000000..16ed5984594ff --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.container.js @@ -0,0 +1,42 @@ +/* + * 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 { connect } from 'react-redux'; +import { DetailPanel as DetailPanelView } from './detail_panel'; + +import { + getDetailPanelType, + getDetailPanelJob, +} from '../../../store/selectors'; + +import { + closeDetailPanel, + openDetailPanel, +} from '../../../store/actions'; + +const mapStateToProps = (state) => { + const job = getDetailPanelJob(state); + return { + panelType: getDetailPanelType(state), + job, + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + closeDetailPanel: () => { + dispatch(closeDetailPanel()); + }, + openDetailPanel: ({ panelType, job }) => { + dispatch(openDetailPanel({ panelType, job })); + }, + }; +}; + +export const DetailPanel = connect( + mapStateToProps, + mapDispatchToProps +)(DetailPanelView); 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 new file mode 100644 index 0000000000000..5234efcbde867 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js @@ -0,0 +1,157 @@ +/* + * 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 } from 'react'; + +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiTab, + EuiTabs, + EuiSpacer, +} from '@elastic/eui'; + +import { + TabSummary, + TabTerms, + TabMetrics, + TabJson, + TabHistogram, +} from './tabs'; + +const tabs = ['Summary', 'Terms', 'Histogram', 'Metrics', 'JSON']; + +export class DetailPanel extends Component { + static defaultProps = { + job: {}, + } + + constructor(props) { + super(props); + } + + renderTabs() { + const { panelType, job, openDetailPanel } = this.props; + + return tabs.map((tab, index) => { + if (tab === 'Terms' && !job.terms.fields.length) { + return; + } + + if (tab === 'Histogram' && !job.histogram.fields.length) { + return; + } + + if (tab === 'Metrics' && !job.metrics.length) { + return; + } + + const isSelected = tab === panelType; + return ( + openDetailPanel({ panelType: tab, job })} + isSelected={isSelected} + data-test-subj={`detailPanelTab${isSelected ? 'Selected' : ''}`} + key={index} + > + {tab} + + ); + }).filter(tab => tab); + } + + render() { + const { + panelType, + closeDetailPanel, + job: { + id, + indexPattern, + rollupIndex, + rollupCron, + rollupInterval, + rollupDelay, + dateHistogramTimeZone, + dateHistogramField, + metrics, + terms, + histogram, + status, + documentsProcessed, + pagesProcessed, + rollupsIndexed, + triggerCount, + json, + }, + } = this.props; + + if (!panelType) { + return null; + } + + const tabToContentMap = { + Summary: ( + + ), + Terms: ( + + ), + Histogram: ( + + ), + Metrics: ( + + ), + JSON: ( + + ), + }; + + const content = tabToContentMap[panelType]; + + return ( + + + +

{id}

+
+ + + + + {this.renderTabs()} + +
+ + + {content} + +
+ ); + } +} diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/index.js new file mode 100644 index 0000000000000..c27bbd8ea830f --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/index.js @@ -0,0 +1,7 @@ +/* + * 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 { DetailPanel } from './detail_panel.container'; 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/job_list/detail_panel/tabs/index.js new file mode 100644 index 0000000000000..425acabd5ec47 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/index.js @@ -0,0 +1,11 @@ +/* + * 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 { TabSummary } from './tab_summary'; +export { TabTerms } from './tab_terms'; +export { TabHistogram } from './tab_histogram'; +export { TabMetrics } from './tab_metrics'; +export { TabJson } from './tab_json'; 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/job_list/detail_panel/tabs/tab_histogram.js new file mode 100644 index 0000000000000..6af04d50b5114 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_histogram.js @@ -0,0 +1,55 @@ +/* + * 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 { + EuiTitle, + EuiSpacer, + EuiText, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +export const TabHistogram = ({ histogram }) => { + const { interval, fields } = histogram; + + // TODO: Render a table if there are more than 20 fields. + + const renderedTerms = fields.map(field => { + return ( +
  • + {field} +
  • + ); + }); + + return ( + + + + +

    Histogram

    +
    +
    + + + +

    Interval: {interval}

    +
    +
    +
    + + + + +
      + {renderedTerms} +
    +
    +
    + ); +}; 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/job_list/detail_panel/tabs/tab_json.js new file mode 100644 index 0000000000000..49608e9a8a3c9 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_json.js @@ -0,0 +1,40 @@ +/* + * 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 { + EuiTitle, + EuiSpacer, + EuiCodeEditor, +} from '@elastic/eui'; + +export const TabJson = ({ + json, +}) => { + const jsonString = JSON.stringify(json, null, 2); + + return ( + + +

    JSON

    +
    + + + + +
    + ); +}; 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/job_list/detail_panel/tabs/tab_metrics.js new file mode 100644 index 0000000000000..f57ac6290f540 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_metrics.js @@ -0,0 +1,40 @@ +/* + * 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, + EuiTitle, + EuiSpacer, +} from '@elastic/eui'; + +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; + + return { + title: field, + description: aggTypes.join(', '), + }; + }); + + return ( + + +

    Metrics

    +
    + + + + +
    + ); +}; 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 new file mode 100644 index 0000000000000..9160cca0ecb1d --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_summary.js @@ -0,0 +1,176 @@ +/* + * 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, + rollupInterval, + rollupDelay, + 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 + + + + {rollupDelay || 'None'} + + + + Interval{' '} + + + + + {rollupInterval} + + + + + + +

    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/detail_panel/tabs/tab_terms.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js new file mode 100644 index 0000000000000..5dc016848a76c --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/tabs/tab_terms.js @@ -0,0 +1,41 @@ +/* + * 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 { + EuiTitle, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +export const TabTerms = ({ terms }) => { + // TODO: Render a table if there are more than 20 fields. + + const renderedTerms = terms.fields.map(field => { + return ( +
  • + {field} +
  • + ); + }); + + return ( + + +

    Terms

    +
    + + + + +
      + {renderedTerms} +
    +
    +
    + ); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/index.js new file mode 100644 index 0000000000000..7a75b1d6bfe70 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/index.js @@ -0,0 +1,7 @@ +/* + * 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 { JobList } from './job_list.container'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js new file mode 100644 index 0000000000000..27e9b855f376c --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js @@ -0,0 +1,22 @@ +/* + * 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 { connect } from 'react-redux'; +import { JobList as JobListView } from './job_list'; + +import { + loadJobs, +} from '../../store/actions'; + +const mapDispatchToProps = (dispatch) => { + return { + loadJobs: () => { + dispatch(loadJobs()); + }, + }; +}; + +export const JobList = connect(null, mapDispatchToProps)(JobListView); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js new file mode 100644 index 0000000000000..8fffe520a9721 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -0,0 +1,45 @@ +/* + * 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, { PureComponent, Fragment } from 'react'; +import PropTypes from 'prop-types'; + +import { + JobTable, +} from './job_table'; + +import { + DetailPanel, +} from './detail_panel'; + +const REFRESH_RATE_MS = 30000; + +export class JobList extends PureComponent { + static propTypes = { + loadJobs: PropTypes.func, + } + + componentWillMount() { + this.props.loadJobs(); + } + + componentDidMount() { + this.interval = setInterval(this.props.loadJobs, REFRESH_RATE_MS); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + render() { + return ( + + + + + ); + } +} 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/job_list/job_status/index.js new file mode 100644 index 0000000000000..acc2584ff76d5 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/index.js @@ -0,0 +1,7 @@ +/* + * 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 { JobStatus } from './job_status'; 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/job_list/job_status/job_status.js new file mode 100644 index 0000000000000..1bd9c4821c54c --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_status/job_status.js @@ -0,0 +1,36 @@ +/* + * 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 { + EuiHealth, +} from '@elastic/eui'; + +const statusToHealthMap = { + stopped: ( + + Stopped + + ), + started: ( + + Started + + ), + indexing: ( + + Indexing + + ), + abort: ( + + Aborting + + ), +}; + +export const JobStatus = ({ status }) => statusToHealthMap[status]; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/index.js new file mode 100644 index 0000000000000..1c771fb61e25c --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/index.js @@ -0,0 +1,7 @@ +/* + * 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 { JobTable } from './job_table.container'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js new file mode 100644 index 0000000000000..3f3d4b4f236a6 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.container.js @@ -0,0 +1,60 @@ +/* + * 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 { connect } from 'react-redux'; + +import { + getPageOfJobs, + getPager, + getFilter, + getSortField, + isSortAscending +} from '../../../store/selectors'; + +import { + filterChanged, + openDetailPanel, + pageChanged, + pageSizeChanged, + sortChanged, +} from '../../../store/actions'; + +import { JobTable as JobTableComponent } from './job_table'; + +const mapStateToProps = (state) => { + return { + jobs: getPageOfJobs(state), + pager: getPager(state), + filter: getFilter(state), + sortField: getSortField(state), + isSortAscending: isSortAscending(state) + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + filterChanged: (filter) => { + dispatch(filterChanged({ filter })); + }, + pageChanged: (pageNumber) => { + dispatch(pageChanged({ pageNumber })); + }, + pageSizeChanged: (pageSize) => { + dispatch(pageSizeChanged({ pageSize })); + }, + sortChanged: (sortField, isSortAscending) => { + dispatch(sortChanged({ sortField, isSortAscending })); + }, + openDetailPanel: (job) => { + dispatch(openDetailPanel({ job })); + }, + }; +}; + +export const JobTable = connect( + mapStateToProps, + mapDispatchToProps +)(JobTableComponent); 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 new file mode 100644 index 0000000000000..0318fe7a805e7 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js @@ -0,0 +1,256 @@ +/* + * 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 } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiFieldSearch, + EuiPage, + EuiSpacer, + EuiTable, + EuiTableBody, + EuiTableHeader, + EuiTableHeaderCell, + EuiTablePagination, + EuiTableRow, + EuiTableRowCell, + EuiTitle, + EuiPageBody, + EuiPageContent, + EuiToolTip, + EuiLink, +} from '@elastic/eui'; + +import { JobStatus } from '../job_status'; + +const COLUMNS = [{ + name: 'ID', + fieldName: 'id', +}, { + name: 'Status', + fieldName: 'status', + render: ({ status, rollupCron }) => { + return ( + + + + ); + }, +}, { + name: 'Index pattern', + truncateText: true, + fieldName: 'indexPattern', +}, { + name: 'Rollup index', + truncateText: true, + fieldName: 'rollupIndex', +}, { + name: 'Delay', + fieldName: 'rollupDelay', + render: ({ rollupDelay }) => rollupDelay || 'None', +}, { + name: 'Interval', + fieldName: 'rollupInterval', +}, { + name: 'Groups', + truncateText: true, + render: job => { + const { histogram, terms } = job; + const humanizedGroupNames = []; + + if (histogram) { + humanizedGroupNames.push('histogram'); + } + + if (terms.fields.length) { + humanizedGroupNames.push('terms'); + } + + if (humanizedGroupNames.length) { + humanizedGroupNames[0] = humanizedGroupNames[0].replace(/^\w/, char => char.toUpperCase()); + return humanizedGroupNames.join(', '); + } + + return 'None'; + }, +}, { + name: 'Metrics', + truncateText: true, + render: job => { + const { metrics } = job; + return metrics.map(metric => metric.field).join(', '); + }, +}]; + +export class JobTable extends Component { + constructor(props) { + super(props); + } + + static propTypes = { + jobs: PropTypes.array, + } + + static defaultProps = { + jobs: [], + } + + onSort = column => { + const { sortField, isSortAscending, sortChanged } = this.props; + + const newIsSortAscending = sortField === column ? !isSortAscending : true; + sortChanged(column, newIsSortAscending); + }; + + buildHeader() { + const { sortField, isSortAscending } = this.props; + return COLUMNS.map(({ name, fieldName }) => { + const isSorted = sortField === fieldName; + + return ( + this.onSort(fieldName) : undefined} + isSorted={isSorted} + isSortAscending={isSortAscending} + data-test-subj={`jobTableHeaderCell-${name}`} + > + {name} + + ); + }); + } + + buildRowCells(job) { + const { openDetailPanel } = this.props; + + return COLUMNS.map(({ name, fieldName, render, truncateText }) => { + const value = render ? render(job) : job[fieldName]; + let content; + + if (name === 'ID') { + content = ( + { + openDetailPanel(job); + }} + > + {value} + + ); + } else { + content = {value}; + } + + let wrappedContent; + + if (truncateText) { + wrappedContent = ( + + {content} + + ); + } else { + wrappedContent = content; + } + + return ( + + {wrappedContent} + + ); + }); + } + + buildRows() { + const { jobs } = this.props; + + return jobs.map(job => { + return ( + + {this.buildRowCells(job)} + + ); + }); + } + + renderPager() { + const { pager, pageChanged, pageSizeChanged } = this.props; + return ( + + ); + } + + render() { + const { + filterChanged, + filter, + jobs + } = this.props; + + return ( + + + + +

    Rollup jobs

    +
    + + + + { + filterChanged(event.target.value); + }} + data-test-subj="jobTableFilterInput" + placeholder="Search" + aria-label="Search jobs" + /> + + + + {jobs.length > 0 ? ( + + + {this.buildHeader()} + + + + {this.buildRows()} + + + ) : ( +
    + No job rollup jobs to show +
    + )} + + + + {jobs.length > 0 ? this.renderPager() : null} +
    +
    +
    + ); + } +} diff --git a/x-pack/plugins/rollup/public/crud_app/services/api.js b/x-pack/plugins/rollup/public/crud_app/services/api.js new file mode 100644 index 0000000000000..0af3a05a76239 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/services/api.js @@ -0,0 +1,20 @@ +/* + * 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 chrome from 'ui/chrome'; + +let httpClient; + +export const setHttpClient = (client) => { + httpClient = client; +}; + +const apiPrefix = chrome.addBasePath('/api/rollup'); + +export async function loadJobs() { + const { data: { jobs } } = await httpClient.get(`${apiPrefix}/jobs`); + return jobs; +} diff --git a/x-pack/plugins/rollup/public/crud_app/services/filter_items.js b/x-pack/plugins/rollup/public/crud_app/services/filter_items.js new file mode 100644 index 0000000000000..7fb3fb8e5a61c --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/services/filter_items.js @@ -0,0 +1,17 @@ +/* + * 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 const filterItems = (fields, filter = '', items = []) => { + const normalizedFilter = filter.toLowerCase(); + return items.filter(item => { + const actualFields = fields || Object.keys(item); + const indexOfMatch = actualFields.findIndex(field => { + const normalizedField = String(item[field]).toLowerCase(); + return normalizedField.includes(normalizedFilter); + }); + return indexOfMatch !== -1; + }); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/services/index.js b/x-pack/plugins/rollup/public/crud_app/services/index.js new file mode 100644 index 0000000000000..1c2eb791a9912 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/services/index.js @@ -0,0 +1,13 @@ +/* + * 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 { + loadJobs, + setHttpClient, +} from './api'; +export { sortTable } from './sort_table'; +export { filterItems } from './filter_items'; +export { deserializeJobs } from './jobs'; diff --git a/x-pack/plugins/rollup/public/crud_app/services/jobs.js b/x-pack/plugins/rollup/public/crud_app/services/jobs.js new file mode 100644 index 0000000000000..9cf981d758bdf --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/services/jobs.js @@ -0,0 +1,66 @@ +/* + * 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. + */ + +function deserializeJob(job) { + const { + config: { + id, + index_pattern: indexPattern, + rollup_index: rollupIndex, + cron: rollupCron, + metrics = [], + groups: { + date_histogram: { + interval: rollupInterval, + delay: rollupDelay, + time_zone: dateHistogramTimeZone, + field: dateHistogramField, + }, + terms = { + fields: [], + }, + histogram = { + fields: [], + }, + }, + }, + status: { + job_state: status, + }, + stats: { + documents_processed: documentsProcessed, + pages_processed: pagesProcessed, + rollups_indexed: rollupsIndexed, + trigger_count: triggerCount, + }, + } = job; + + const json = job; + + return { + id, + indexPattern, + rollupIndex, + rollupCron, + rollupInterval, + rollupDelay, + dateHistogramTimeZone, + dateHistogramField, + metrics, + terms, + histogram, + status, + documentsProcessed, + pagesProcessed, + rollupsIndexed, + triggerCount, + json, + }; +} + +export function deserializeJobs(jobs) { + return jobs.map(deserializeJob); +} diff --git a/x-pack/plugins/rollup/public/crud_app/services/sort_table.js b/x-pack/plugins/rollup/public/crud_app/services/sort_table.js new file mode 100644 index 0000000000000..6af8e60e063f1 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/services/sort_table.js @@ -0,0 +1,19 @@ +/* + * 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 { sortBy } from 'lodash'; + +const stringSort = (fieldName) => (item) => item[fieldName]; + +const sorters = {}; + +export const sortTable = (array = [], sortField, isSortAscending) => { + const sorter = sorters[sortField] || stringSort(sortField); + const sorted = sortBy(array, sorter); + return isSortAscending + ? sorted + : sorted.reverse(); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js new file mode 100644 index 0000000000000..d4310f048803e --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.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. + */ + +import { createAction } from 'redux-actions'; + +export const openDetailPanel = createAction( + 'INDEX_ROLLUP_JOB_OPEN_DETAIL_PANEL' +); +export const closeDetailPanel = createAction( + 'INDEX_ROLLUP_JOB_CLOSE_DETAIL_PANEL' +); diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/index.js b/x-pack/plugins/rollup/public/crud_app/store/actions/index.js new file mode 100644 index 0000000000000..43f3f44acfeff --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/index.js @@ -0,0 +1,24 @@ +/* + * 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 { + loadJobs, + loadJobsSuccess, +} from './load_jobs'; + +export { + applyFilters, + filtersApplied, + filterChanged, + pageChanged, + pageSizeChanged, + sortChanged, +} from './table_state'; + +export { + openDetailPanel, + closeDetailPanel, +} from './detail_panel'; diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js new file mode 100644 index 0000000000000..10b52f00abeaf --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/load_jobs.js @@ -0,0 +1,21 @@ +/* + * 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 { createAction } from 'redux-actions'; +import { toastNotifications } from 'ui/notify'; +import { loadJobs as request, deserializeJobs } from '../../services'; + +export const loadJobsSuccess = createAction('LOAD_JOBS_SUCCESS'); +export const loadJobs = () => async (dispatch) => { + let jobs; + try { + jobs = await request(); + } catch (error) { + return toastNotifications.addDanger(error.data.message); + } + + dispatch(loadJobsSuccess({ jobs: deserializeJobs(jobs) })); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js new file mode 100644 index 0000000000000..5b98b6fadc706 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js @@ -0,0 +1,22 @@ +/* + * 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 { createAction } from 'redux-actions'; + +export const applyFilters = createAction('APPLY_FILTERS'); +export const filtersApplied = createAction('FILTERS_APPLIED'); + +export const filterChanged = + createAction('FILTER_CHANGED'); + +export const pageChanged = + createAction('PAGE_CHANGED'); + +export const pageSizeChanged = + createAction('PAGE_SIZE_CHANGED'); + +export const sortChanged = + createAction('SORT_CHANGED'); diff --git a/x-pack/plugins/rollup/public/crud_app/store/index.js b/x-pack/plugins/rollup/public/crud_app/store/index.js new file mode 100644 index 0000000000000..bd072ee94d022 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/index.js @@ -0,0 +1,7 @@ +/* + * 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 { rollupJobsStore } from './store'; diff --git a/x-pack/plugins/rollup/public/crud_app/store/reducers/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/store/reducers/detail_panel.js new file mode 100644 index 0000000000000..e38d218ef0f9a --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/reducers/detail_panel.js @@ -0,0 +1,30 @@ +/* + * 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 { handleActions } from 'redux-actions'; +import { openDetailPanel, closeDetailPanel } from '../actions/detail_panel'; + +const defaultState = {}; + +export const detailPanel = handleActions( + { + [openDetailPanel](state, action) { + const { + panelType, + job, + } = action.payload; + + return { + panelType: panelType || state.panelType || 'Summary', + job, + }; + }, + [closeDetailPanel]() { + return {}; + }, + }, + defaultState +); diff --git a/x-pack/plugins/rollup/public/crud_app/store/reducers/index.js b/x-pack/plugins/rollup/public/crud_app/store/reducers/index.js new file mode 100644 index 0000000000000..cf5935c6a7a96 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/reducers/index.js @@ -0,0 +1,16 @@ +/* + * 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 { combineReducers } from 'redux'; +import { jobs } from './jobs'; +import { tableState } from './table_state'; +import { detailPanel } from './detail_panel'; + +export const rollupJobs = combineReducers({ + jobs, + tableState, + detailPanel, +}); diff --git a/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js b/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.js new file mode 100644 index 0000000000000..b57e8c9199fa3 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/reducers/jobs.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 { combineReducers } from 'redux'; +import { handleActions } from 'redux-actions'; +import { + loadJobsSuccess, +} from '../actions'; + +const byId = handleActions({ + [loadJobsSuccess](state, action) { + const { jobs } = action.payload; + const newState = {}; + jobs.forEach(job => { + newState[job.id] = job; + }); + + return newState; + }, +}, {}); + +const allIds = handleActions({ + [loadJobsSuccess](state, action) { + const { jobs } = action.payload; + return jobs.map(job => job.id); + }, +}, []); + +export const jobs = combineReducers({ + byId, + allIds, +}); diff --git a/x-pack/plugins/rollup/public/crud_app/store/reducers/table_state.js b/x-pack/plugins/rollup/public/crud_app/store/reducers/table_state.js new file mode 100644 index 0000000000000..2ea7477b1a7e6 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/reducers/table_state.js @@ -0,0 +1,55 @@ +/* + * 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 { handleActions } from 'redux-actions'; +import { + filterChanged, + pageChanged, + pageSizeChanged, + sortChanged, +} from '../actions'; + +const defaultState = { + filter: '', + pageSize: 10, + currentPage: 0, + sortField: 'job.id', + isSortAscending: true, +}; + +export const tableState = handleActions({ + [filterChanged](state, action) { + const { filter } = action.payload; + return { + ...state, + filter, + currentPage: 0 + }; + }, + [sortChanged](state, action) { + const { sortField, isSortAscending } = action.payload; + + return { + ...state, + sortField, + isSortAscending, + }; + }, + [pageChanged](state, action) { + const { pageNumber } = action.payload; + return { + ...state, + currentPage: pageNumber, + }; + }, + [pageSizeChanged](state, action) { + const { pageSize } = action.payload; + return { + ...state, + pageSize + }; + } +}, defaultState); diff --git a/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js b/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js new file mode 100644 index 0000000000000..cdab30e9c4e9a --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/selectors/index.js @@ -0,0 +1,97 @@ +/* + * 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 { Pager } from '@elastic/eui'; + +import { createSelector } from 'reselect'; +import { filterItems, sortTable } from '../../services'; + +export const getDetailPanelType = (state) => state.detailPanel.panelType; +export const isDetailPanelOpen = (state) => !!getDetailPanelType(state); +export const getDetailPanelJob = (state) => state.detailPanel.job; + +export const getJobs = (state) => state.jobs.byId; +export const getJobByJobName = (state, name) => getJobs(state)[name]; +export const getFilteredIds = (state) => state.jobs.filteredIds; +export const getTableState = (state) => state.tableState; + +export const getJobStatusByJobName = (state, jobName) => { + const jobs = getJobs(state); + const { status } = jobs[jobName] || {}; + return status; +}; + +const getFilteredJobs = createSelector( + getJobs, + getTableState, + (jobs, tableState) => { + const jobArray = Object.keys(jobs).map(jobName => jobs[jobName]); + return filterItems(['id', 'indexPattern', 'rollupIndex'], tableState.filter, jobArray); + } +); + +export const getTotalItems = createSelector( + getFilteredJobs, + (filteredJobs) => { + return Object.keys(filteredJobs).length; + } +); + +export const getPager = createSelector( + getTableState, + getTotalItems, + ({ currentPage, pageSize }, totalItems) => { + return new Pager(totalItems, pageSize, currentPage); + } +); + +export const getPageOfJobs = createSelector( + getFilteredJobs, + getTableState, + getPager, + (filteredJobs, tableState, pager) => { + const sortedIndexes = sortTable(filteredJobs, tableState.sortField, tableState.isSortAscending); + const { firstItemIndex, lastItemIndex } = pager; + const pagedIndexes = sortedIndexes.slice(firstItemIndex, lastItemIndex + 1); + return pagedIndexes; + } +); + +export const getHasNextPage = createSelector( + getPager, + (pager) => { + return pager.hasNextPage; + } +); + +export const getHasPreviousPage = createSelector( + getPager, + (pager) => { + return pager.hasPreviousPage; + } +); + +export const getCurrentPage = createSelector( + getPager, + (pager) => { + return pager.currentPage; + } +); + +export const getFilter = createSelector( + getTableState, + ({ filter }) => filter +); + +export const isSortAscending = createSelector( + getTableState, + ({ isSortAscending }) => isSortAscending +); + +export const getSortField = createSelector( + getTableState, + ({ sortField }) => sortField +); diff --git a/x-pack/plugins/rollup/public/crud_app/store/store.js b/x-pack/plugins/rollup/public/crud_app/store/store.js new file mode 100644 index 0000000000000..5313a5c012f0b --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/store/store.js @@ -0,0 +1,21 @@ +/* + * 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 { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; + +import { rollupJobs } from './reducers/'; + +export const rollupJobsStore = (initialState = {}) => { + const enhancers = [ applyMiddleware(thunk) ]; + + window.__REDUX_DEVTOOLS_EXTENSION__ && enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); + return createStore( + rollupJobs, + initialState, + compose(...enhancers) + ); +}; diff --git a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.js b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.js index ef9375c2f5322..ca8177c1e50ee 100644 --- a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.js +++ b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.js @@ -55,5 +55,14 @@ export const elasticsearchJsPlugin = (Client, config, components) => { needBody: true, method: 'POST' }); + + rollup.jobs = ca({ + urls: [ + { + fmt: '/_xpack/rollup/job/_all', + } + ], + method: 'GET' + }); }; diff --git a/x-pack/plugins/rollup/server/routes/api/index.js b/x-pack/plugins/rollup/server/routes/api/index.js index 0bf361d6c6399..2807e56d7428b 100644 --- a/x-pack/plugins/rollup/server/routes/api/index.js +++ b/x-pack/plugins/rollup/server/routes/api/index.js @@ -7,3 +7,4 @@ export { registerIndicesRoute } from './indices'; export { registerFieldsForWildcardRoute } from './index_patterns'; export { registerSearchRoute } from './search'; +export { registerJobsRoute } from './jobs'; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs.js b/x-pack/plugins/rollup/server/routes/api/jobs.js new file mode 100644 index 0000000000000..8086e065544fa --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs.js @@ -0,0 +1,31 @@ +/* +* 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 { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { isEsErrorFactory } from '../../lib/is_es_error_factory'; +import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; + +export function registerJobsRoute(server) { + const isEsError = isEsErrorFactory(server); + + server.route({ + path: '/api/rollup/jobs', + method: 'GET', + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + try { + const results = await callWithRequest('rollup.jobs'); + reply(results); + } catch(err) { + if (isEsError(err)) { + return reply(wrapEsError(err)); + } + + reply(wrapUnknownError(err)); + } + }, + }); +}