NP Migration: Rollup plugin (#53503)

* Start shimming rollup plugin

* continued shimming rollup ui

* Remove unnecessarily return

* Register management section

* Replace ui/chrome

* Replace ui/documentation_links

* Replace ui/kfetch and ui/courier

* Start shimming rollup plugin

* continued shimming rollup ui

* Remove unnecessarily return

* Register management section

* Replace ui/chrome

* Replace ui/documentation_links

* Replace ui/kfetch and ui/courier

* Replace ui/notify

* Move ui/ imports to legacy_imports.ts

* Update NP mock for management

* Refactoring

* Read body from error object

* Update setup_environment.js

* Update unit tests

* Get rid of injectI18n

* Replace npStart and npSetup usage to services

* Import search strategy stuff from the top level of the data plugin

* Update unit tests

* Do not prepend the url

* Fix merge conflicts

* Refactoring

* Revert removal of setUserHasLeftApp

* Export getSearchErrorType

* Remove extra wrapper - Router

* Fix cause prop.

* Leave just static imports in legacy_imports.js

* Add TS

* Pass statusCode instead of statusText

* Move template in a separate file

* Move app register to setup

* Add karma mock for management setup

* Add EditorConfigProviderRegistry export

Co-authored-by: Maryia Lapata <mary.lopato@gmail.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Joe Reuter 2020-01-30 09:25:47 +01:00 committed by GitHub
parent 326afbc304
commit 0d03ade9ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 716 additions and 667 deletions

View file

@ -17,5 +17,5 @@
* under the License. * under the License.
*/ */
export { aggTypeFilters } from './agg_type_filters'; export { aggTypeFilters, AggTypeFilters } from './agg_type_filters';
export { propFilter } from './prop_filter'; export { propFilter } from './prop_filter';

View file

@ -17,4 +17,4 @@
* under the License. * under the License.
*/ */
export { aggTypeFieldFilters } from './field_filters'; export { aggTypeFieldFilters, AggTypeFieldFilters } from './field_filters';

View file

@ -147,6 +147,13 @@ export const npSetup = {
useChartsTheme: sinon.fake(), useChartsTheme: sinon.fake(),
}, },
}, },
management: {
sections: {
getSection: () => ({
registerApp: sinon.fake(),
}),
},
},
}, },
}; };
@ -167,6 +174,11 @@ export const npStart = {
hasItem: sinon.fake(), hasItem: sinon.fake(),
}), }),
}, },
sections: {
getSection: () => ({
registerApp: sinon.fake(),
}),
},
}, },
embeddable: { embeddable: {
getEmbeddableFactory: sinon.fake(), getEmbeddableFactory: sinon.fake(),

View file

@ -32,7 +32,7 @@ import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/publ
import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public'; import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public';
import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public'; import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public';
import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public';
import { ManagementStart } from '../../../../plugins/management/public'; import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public';
import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public';
import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
import { import {
@ -54,6 +54,7 @@ export interface PluginsSetup {
kibana_legacy: KibanaLegacySetup; kibana_legacy: KibanaLegacySetup;
share: SharePluginSetup; share: SharePluginSetup;
usageCollection: UsageCollectionSetup; usageCollection: UsageCollectionSetup;
management: ManagementSetup;
} }
export interface PluginsStart { export interface PluginsStart {

View file

@ -17,5 +17,5 @@
* under the License. * under the License.
*/ */
export { editorConfigProviders } from './editor_config_providers'; export { editorConfigProviders, EditorConfigProviderRegistry } from './editor_config_providers';
export * from './types'; export * from './types';

View file

@ -47,6 +47,8 @@ export {
hasSearchStategyForIndexPattern, hasSearchStategyForIndexPattern,
defaultSearchStrategy, defaultSearchStrategy,
SearchError, SearchError,
SearchStrategyProvider,
getSearchErrorType,
} from './search_strategy'; } from './search_strategy';
export { export {

View file

@ -1,70 +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 sinon from 'sinon';
// Register helpers to mock HTTP Requests
const registerHttpRequestMockHelpers = server => {
const setIndexPatternValidityResponse = response => {
const defaultResponse = {
doesMatchIndices: true,
doesMatchRollupIndices: false,
dateFields: ['foo', 'bar'],
numericFields: [],
keywordFields: [],
};
server.respondWith(/\/api\/rollup\/index_pattern_validity\/.*/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({ ...defaultResponse, ...response }),
]);
};
const setCreateJobResponse = (responsePayload = {}) => {
server.respondWith(/\/api\/rollup\/create/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(responsePayload),
]);
};
const setStartJobResponse = () => {
server.respondWith(/\/api\/rollup\/start/, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({}),
]);
};
const setLoadJobsResponse = response => {
server.respondWith('GET', '/api/rollup/jobs', [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
]);
};
return {
setIndexPatternValidityResponse,
setCreateJobResponse,
setLoadJobsResponse,
setStartJobResponse,
};
};
export const init = () => {
const server = sinon.fakeServer.create();
server.respondImmediately = true;
// We make requests to APIs which don't impact the UX, e.g. UI metric telemetry,
// and we can mock them all with a 200 instead of mocking each one individually.
server.respondWith([200, {}, '']);
return {
server,
httpRequestsMockHelpers: registerHttpRequestMockHelpers(server),
};
};

View file

@ -10,7 +10,9 @@ import { setup as jobCloneSetup } from './job_clone.helpers';
export { nextTick, getRandomString, findTestSubject } from '../../../../../../test_utils'; export { nextTick, getRandomString, findTestSubject } from '../../../../../../test_utils';
export { setupEnvironment } from './setup_environment'; export { mockHttpRequest } from './setup_environment';
export { wrapComponent } from './setup_context';
export const pageHelpers = { export const pageHelpers = {
jobCreate: { setup: jobCreateSetup }, jobCreate: { setup: jobCreateSetup },

View file

@ -10,8 +10,10 @@ import { JobCreate } from '../../../public/crud_app/sections';
import { JOB_TO_CLONE } from './constants'; import { JOB_TO_CLONE } from './constants';
import { deserializeJob } from '../../../public/crud_app/services'; import { deserializeJob } from '../../../public/crud_app/services';
import { wrapComponent } from './setup_context';
export const setup = props => { export const setup = props => {
const initTestBed = registerTestBed(JobCreate, { const initTestBed = registerTestBed(wrapComponent(JobCreate), {
store: createRollupJobsStore({ store: createRollupJobsStore({
cloneJob: { job: deserializeJob(JOB_TO_CLONE.jobs[0]) }, cloneJob: { job: deserializeJob(JOB_TO_CLONE.jobs[0]) },
}), }),

View file

@ -10,7 +10,9 @@ import { JobCreate } from '../../../public/crud_app/sections';
import { JOB_TO_CREATE } from './constants'; import { JOB_TO_CREATE } from './constants';
const initTestBed = registerTestBed(JobCreate, { store: rollupJobsStore }); import { wrapComponent } from './setup_context';
const initTestBed = registerTestBed(wrapComponent(JobCreate), { store: rollupJobsStore });
export const setup = props => { export const setup = props => {
const testBed = initTestBed(props); const testBed = initTestBed(props);

View file

@ -9,6 +9,8 @@ import { registerRouter } from '../../../public/crud_app/services';
import { createRollupJobsStore } from '../../../public/crud_app/store'; import { createRollupJobsStore } from '../../../public/crud_app/store';
import { JobList } from '../../../public/crud_app/sections/job_list'; import { JobList } from '../../../public/crud_app/sections/job_list';
import { wrapComponent } from './setup_context';
const testBedConfig = { const testBedConfig = {
store: createRollupJobsStore, store: createRollupJobsStore,
memoryRouter: { memoryRouter: {
@ -19,4 +21,4 @@ const testBedConfig = {
}, },
}; };
export const setup = registerTestBed(JobList, testBedConfig); export const setup = registerTestBed(wrapComponent(JobList), testBedConfig);

View file

@ -0,0 +1,23 @@
/*
* 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, { FunctionComponent } from 'react';
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
import { coreMock } from '../../../../../../../src/core/public/mocks';
const startMock = coreMock.createStart();
const services = {
setBreadcrumbs: startMock.chrome.setBreadcrumbs,
};
const wrapComponent = (Component: FunctionComponent) => (props: any) => (
<KibanaContextProvider services={services}>
<Component {...props} />
</KibanaContextProvider>
);
export { wrapComponent };

View file

@ -1,23 +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 axios from 'axios';
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
import { setHttp } from '../../../public/crud_app/services';
import { init as initHttpRequests } from './http_requests';
export const setupEnvironment = () => {
// axios has a $http like interface so using it to simulate $http
setHttp(axios.create({ adapter: axiosXhrAdapter }));
const { server, httpRequestsMockHelpers } = initHttpRequests();
return {
server,
httpRequestsMockHelpers,
};
};

View file

@ -0,0 +1,44 @@
/*
* 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.
*/
interface RequestMocks {
jobs?: object;
createdJob?: object;
indxPatternVldtResp?: object;
[key: string]: any;
}
const mockHttpRequest = (
http: any,
{ jobs = {}, createdJob = {}, indxPatternVldtResp = {} }: RequestMocks = {}
) => {
http.get.mockImplementation(async (url: string) => {
if (url === '/api/rollup/jobs') {
return jobs;
}
if (url.startsWith('/api/rollup/index_pattern_validity')) {
return {
doesMatchIndices: true,
doesMatchRollupIndices: false,
dateFields: ['foo', 'bar'],
numericFields: [],
keywordFields: [],
...indxPatternVldtResp,
};
}
return {};
});
// mock '/api/rollup/start'
http.post.mockImplementation(async (url: string) => ({}));
// mock '/api/rollup/create
http.put.mockImplementation(async (url: string) => createdJob);
};
export { mockHttpRequest };

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers, nextTick } from './helpers';
import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -17,26 +18,28 @@ const {
} = JOB_TO_CLONE; } = JOB_TO_CLONE;
describe('Cloning a rollup job through create job wizard', () => { describe('Cloning a rollup job through create job wizard', () => {
let httpRequestsMockHelpers;
let server;
let find; let find;
let exists; let exists;
let form; let form;
let table; let table;
let actions; let actions;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
}); });
beforeEach(() => { beforeEach(() => {
httpRequestsMockHelpers.setIndexPatternValidityResponse(JOB_CLONE_INDEX_PATTERN_CHECK); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: JOB_CLONE_INDEX_PATTERN_CHECK });
({ exists, find, form, actions, table } = setup()); ({ exists, find, form, actions, table } = setup());
}); });
afterAll(() => { afterEach(() => {
server.restore(); npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
}); });
it('should have fields correctly pre-populated', async () => { it('should have fields correctly pre-populated', async () => {

View file

@ -6,7 +6,8 @@
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { setupEnvironment, pageHelpers } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -15,30 +16,31 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 2: Date histogram', () => { describe('Create Rollup Job, step 2: Date histogram', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
let goToStep; let goToStep;
let form; let form;
let getEuiStepsHorizontalActive; let getEuiStepsHorizontalActive;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
}); });
afterAll(() => {
server.restore();
});
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, form, getEuiStepsHorizontalActive, goToStep } = setup()); ({ find, exists, actions, form, getEuiStepsHorizontalActive, goToStep } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
describe('layout', () => { describe('layout', () => {
beforeEach(async () => { beforeEach(async () => {
await goToStep(2); await goToStep(2);
@ -71,7 +73,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => {
describe('Date field select', () => { describe('Date field select', () => {
it('should set the options value from the index pattern', async () => { it('should set the options value from the index pattern', async () => {
const dateFields = ['field1', 'field2', 'field3']; const dateFields = ['field1', 'field2', 'field3'];
httpRequestsMockHelpers.setIndexPatternValidityResponse({ dateFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { dateFields } });
await goToStep(2); await goToStep(2);
@ -83,7 +85,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => {
it('should sort the options in ascending order', async () => { it('should sort the options in ascending order', async () => {
const dateFields = ['field3', 'field2', 'field1']; const dateFields = ['field3', 'field2', 'field1'];
httpRequestsMockHelpers.setIndexPatternValidityResponse({ dateFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { dateFields } });
await goToStep(2); await goToStep(2);

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -13,8 +14,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 4: Histogram', () => { describe('Create Rollup Job, step 4: Histogram', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
@ -22,22 +21,26 @@ describe('Create Rollup Job, step 4: Histogram', () => {
let goToStep; let goToStep;
let table; let table;
let form; let form;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, form } = setup()); ({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, form } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
const numericFields = ['a-numericField', 'b-numericField']; const numericFields = ['a-numericField', 'b-numericField'];
const goToStepAndOpenFieldChooser = async () => { const goToStepAndOpenFieldChooser = async () => {
@ -108,7 +111,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
describe('when no histogram fields are availalbe', () => { describe('when no histogram fields are availalbe', () => {
it('should indicate it to the user', async () => { it('should indicate it to the user', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: [] }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: [] } });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
const { tableCellsValues } = table.getMetaData('rollupJobHistogramFieldChooser-table'); const { tableCellsValues } = table.getMetaData('rollupJobHistogramFieldChooser-table');
@ -119,7 +122,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
describe('when histogram fields are available', () => { describe('when histogram fields are available', () => {
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
}); });
@ -153,7 +156,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
it('should have a delete button on each row to remove an histogram field', async () => { it('should have a delete button on each row to remove an histogram field', async () => {
// First let's add a term to the list // First let's add a term to the list
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = table.getMetaData('rollupJobHistogramFieldChooser-table'); const { rows: fieldChooserRows } = table.getMetaData('rollupJobHistogramFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click'); fieldChooserRows[0].reactWrapper.simulate('click');
@ -180,7 +183,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
}; };
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
await goToStep(4); await goToStep(4);
addHistogramFieldToList(); addHistogramFieldToList();
}); });

View file

@ -13,7 +13,8 @@ import {
YEAR, YEAR,
} from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; } from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor';
import { indexPatterns } from '../../../../../../src/plugins/data/public'; import { indexPatterns } from '../../../../../../src/plugins/data/public';
import { setupEnvironment, pageHelpers } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -22,29 +23,31 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 1: Logistics', () => { describe('Create Rollup Job, step 1: Logistics', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
let form; let form;
let getEuiStepsHorizontalActive; let getEuiStepsHorizontalActive;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, form, getEuiStepsHorizontalActive } = setup()); ({ find, exists, actions, form, getEuiStepsHorizontalActive } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
it('should have the horizontal step active on "Logistics"', () => { it('should have the horizontal step active on "Logistics"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Logistics'); expect(getEuiStepsHorizontalActive()).toContain('Logistics');
}); });
@ -94,14 +97,14 @@ describe('Create Rollup Job, step 1: Logistics', () => {
}); });
it('should not allow an unknown index pattern', async () => { it('should not allow an unknown index pattern', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ doesMatchIndices: false }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { doesMatchIndices: false } });
await form.setInputValue('rollupIndexPattern', 'unknown', true); await form.setInputValue('rollupIndexPattern', 'unknown', true);
actions.clickNextStep(); actions.clickNextStep();
expect(form.getErrorsMessages()).toContain("Index pattern doesn't match any indices."); expect(form.getErrorsMessages()).toContain("Index pattern doesn't match any indices.");
}); });
it('should not allow an index pattern without time fields', async () => { it('should not allow an index pattern without time fields', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ dateFields: [] }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { dateFields: [] } });
await form.setInputValue('rollupIndexPattern', 'abc', true); await form.setInputValue('rollupIndexPattern', 'abc', true);
actions.clickNextStep(); actions.clickNextStep();
expect(form.getErrorsMessages()).toContain( expect(form.getErrorsMessages()).toContain(
@ -110,7 +113,9 @@ describe('Create Rollup Job, step 1: Logistics', () => {
}); });
it('should not allow an index pattern that matches a rollup index', async () => { it('should not allow an index pattern that matches a rollup index', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ doesMatchRollupIndices: true }); mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: { doesMatchRollupIndices: true },
});
await form.setInputValue('rollupIndexPattern', 'abc', true); await form.setInputValue('rollupIndexPattern', 'abc', true);
actions.clickNextStep(); actions.clickNextStep();
expect(form.getErrorsMessages()).toContain('Index pattern must not match rollup indices.'); expect(form.getErrorsMessages()).toContain('Index pattern must not match rollup indices.');

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -13,8 +14,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 5: Metrics', () => { describe('Create Rollup Job, step 5: Metrics', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
@ -22,22 +21,26 @@ describe('Create Rollup Job, step 5: Metrics', () => {
let goToStep; let goToStep;
let table; let table;
let metrics; let metrics;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, metrics } = setup()); ({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, metrics } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
const numericFields = ['a-numericField', 'c-numericField']; const numericFields = ['a-numericField', 'c-numericField'];
const dateFields = ['b-dateField', 'd-dateField']; const dateFields = ['b-dateField', 'd-dateField'];
@ -109,7 +112,7 @@ describe('Create Rollup Job, step 5: Metrics', () => {
describe('table', () => { describe('table', () => {
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
}); });
@ -166,7 +169,7 @@ describe('Create Rollup Job, step 5: Metrics', () => {
describe('when fields are added', () => { describe('when fields are added', () => {
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
}); });
@ -257,7 +260,8 @@ describe('Create Rollup Job, step 5: Metrics', () => {
let getFieldListTableRows; let getFieldListTableRows;
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStep(5); await goToStep(5);
await addFieldToList('numeric'); await addFieldToList('numeric');
await addFieldToList('date'); await addFieldToList('date');

View file

@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers } from './helpers'; import { pageHelpers, mockHttpRequest } from './helpers';
import { first } from 'lodash'; import { first } from 'lodash';
import { setHttp } from '../../public/crud_app/services';
import { JOBS } from './helpers/constants'; import { JOBS } from './helpers/constants';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -15,8 +16,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 6: Review', () => { describe('Create Rollup Job, step 6: Review', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
@ -24,21 +23,25 @@ describe('Create Rollup Job, step 6: Review', () => {
let goToStep; let goToStep;
let table; let table;
let form; let form;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, form } = setup()); ({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table, form } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
describe('layout', () => { describe('layout', () => {
beforeEach(async () => { beforeEach(async () => {
await goToStep(6); await goToStep(6);
@ -81,7 +84,7 @@ describe('Create Rollup Job, step 6: Review', () => {
}); });
it('should have a "Summary", "Terms" & "Request" tab if a term aggregation was added', async () => { it('should have a "Summary", "Terms" & "Request" tab if a term aggregation was added', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: ['my-field'] }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: ['my-field'] } });
await goToStep(3); await goToStep(3);
selectFirstField('Terms'); selectFirstField('Terms');
@ -93,7 +96,7 @@ describe('Create Rollup Job, step 6: Review', () => {
}); });
it('should have a "Summary", "Histogram" & "Request" tab if a histogram field was added', async () => { it('should have a "Summary", "Histogram" & "Request" tab if a histogram field was added', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: ['a-field'] }); mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: ['a-field'] } });
await goToStep(4); await goToStep(4);
selectFirstField('Histogram'); selectFirstField('Histogram');
form.setInputValue('rollupJobCreateHistogramInterval', 3); // set an interval form.setInputValue('rollupJobCreateHistogramInterval', 3); // set an interval
@ -105,9 +108,11 @@ describe('Create Rollup Job, step 6: Review', () => {
}); });
it('should have a "Summary", "Metrics" & "Request" tab if a histogram field was added', async () => { it('should have a "Summary", "Metrics" & "Request" tab if a histogram field was added', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ mockHttpRequest(npStart.core.http, {
numericFields: ['a-field'], indxPatternVldtResp: {
dateFields: ['b-field'], numericFields: ['a-field'],
dateFields: ['b-field'],
},
}); });
await goToStep(5); await goToStep(5);
selectFirstField('Metrics'); selectFirstField('Metrics');
@ -125,27 +130,30 @@ describe('Create Rollup Job, step 6: Review', () => {
describe('without starting job after creation', () => { describe('without starting job after creation', () => {
it('should call the "create" Api server endpoint', async () => { it('should call the "create" Api server endpoint', async () => {
httpRequestsMockHelpers.setCreateJobResponse(first(JOBS.jobs)); mockHttpRequest(npStart.core.http, {
createdJob: first(JOBS.jobs),
});
await goToStep(6); await goToStep(6);
expect(server.requests.find(r => r.url === jobCreateApiPath)).toBe(undefined); // make sure it hasn't been called expect(npStart.core.http.put).not.toHaveBeenCalledWith(jobCreateApiPath); // make sure it hasn't been called
expect(server.requests.find(r => r.url === jobStartApiPath)).toBe(undefined); // make sure it hasn't been called expect(npStart.core.http.get).not.toHaveBeenCalledWith(jobStartApiPath); // make sure it hasn't been called
actions.clickSave(); actions.clickSave();
// Given the following anti-jitter sleep x-pack/legacy/plugins/rollup/public/crud_app/store/actions/create_job.js // Given the following anti-jitter sleep x-pack/legacy/plugins/rollup/public/crud_app/store/actions/create_job.js
// we add a longer sleep here :( // we add a longer sleep here :(
await new Promise(res => setTimeout(res, 750)); await new Promise(res => setTimeout(res, 750));
expect(server.requests.find(r => r.url === jobCreateApiPath)).not.toBe(undefined); // It has been called! expect(npStart.core.http.put).toHaveBeenCalledWith(jobCreateApiPath, expect.anything()); // It has been called!
expect(server.requests.find(r => r.url === jobStartApiPath)).toBe(undefined); // It has still not been called! expect(npStart.core.http.get).not.toHaveBeenCalledWith(jobStartApiPath); // It has still not been called!
}); });
}); });
describe('with starting job after creation', () => { describe('with starting job after creation', () => {
it('should call the "create" and "start" Api server endpoints', async () => { it('should call the "create" and "start" Api server endpoints', async () => {
httpRequestsMockHelpers.setCreateJobResponse(first(JOBS.jobs)); mockHttpRequest(npStart.core.http, {
httpRequestsMockHelpers.setStartJobResponse(); createdJob: first(JOBS.jobs),
});
await goToStep(6); await goToStep(6);
@ -153,14 +161,14 @@ describe('Create Rollup Job, step 6: Review', () => {
target: { checked: true }, target: { checked: true },
}); });
expect(server.requests.find(r => r.url === jobStartApiPath)).toBe(undefined); // make sure it hasn't been called expect(npStart.core.http.post).not.toHaveBeenCalledWith(jobStartApiPath); // make sure it hasn't been called
actions.clickSave(); actions.clickSave();
// Given the following anti-jitter sleep x-pack/legacy/plugins/rollup/public/crud_app/store/actions/create_job.js // Given the following anti-jitter sleep x-pack/legacy/plugins/rollup/public/crud_app/store/actions/create_job.js
// we add a longer sleep here :( // we add a longer sleep here :(
await new Promise(res => setTimeout(res, 750)); await new Promise(res => setTimeout(res, 750));
expect(server.requests.find(r => r.url === jobStartApiPath)).not.toBe(undefined); // It has been called! expect(npStart.core.http.post).toHaveBeenCalledWith(jobStartApiPath, expect.anything()); // It has been called!
}); });
}); });
}); });

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers } from './helpers'; import { setHttp } from '../../public/crud_app/services';
import { pageHelpers, mockHttpRequest } from './helpers';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -13,30 +14,30 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate; const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 3: Terms', () => { describe('Create Rollup Job, step 3: Terms', () => {
let server;
let httpRequestsMockHelpers;
let find; let find;
let exists; let exists;
let actions; let actions;
let getEuiStepsHorizontalActive; let getEuiStepsHorizontalActive;
let goToStep; let goToStep;
let table; let table;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(() => { beforeEach(() => {
// Set "default" mock responses by not providing any arguments // Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse(); mockHttpRequest(npStart.core.http);
({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table } = setup()); ({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table } = setup());
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
});
const numericFields = ['a-numericField', 'c-numericField']; const numericFields = ['a-numericField', 'c-numericField'];
const keywordFields = ['b-keywordField', 'd-keywordField']; const keywordFields = ['b-keywordField', 'd-keywordField'];
@ -108,9 +109,11 @@ describe('Create Rollup Job, step 3: Terms', () => {
describe('when no terms are available', () => { describe('when no terms are available', () => {
it('should indicate it to the user', async () => { it('should indicate it to the user', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ mockHttpRequest(npStart.core.http, {
numericFields: [], indxPatternVldtResp: {
keywordFields: [], numericFields: [],
keywordFields: [],
},
}); });
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
@ -122,7 +125,12 @@ describe('Create Rollup Job, step 3: Terms', () => {
describe('when terms are available', () => { describe('when terms are available', () => {
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, keywordFields }); mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields,
keywordFields,
},
});
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
}); });
@ -163,7 +171,12 @@ describe('Create Rollup Job, step 3: Terms', () => {
it('should have a delete button on each row to remove a term', async () => { it('should have a delete button on each row to remove a term', async () => {
// First let's add a term to the list // First let's add a term to the list
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, keywordFields }); mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields,
keywordFields,
},
});
await goToStepAndOpenFieldChooser(); await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = table.getMetaData('rollupJobTermsFieldChooser-table'); const { rows: fieldChooserRows } = table.getMetaData('rollupJobTermsFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click'); fieldChooserRows[0].reactWrapper.simulate('click');

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { getRouter } from '../../public/crud_app/services'; import { getRouter, setHttp } from '../../public/crud_app/services';
import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { mockHttpRequest, pageHelpers, nextTick } from './helpers';
import { JOBS } from './helpers/constants'; import { JOBS } from './helpers/constants';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -22,22 +22,18 @@ const { setup } = pageHelpers.jobList;
describe('<JobList />', () => { describe('<JobList />', () => {
describe('detail panel', () => { describe('detail panel', () => {
let server;
let httpRequestsMockHelpers;
let component; let component;
let table; let table;
let exists; let exists;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setLoadJobsResponse(JOBS); mockHttpRequest(npStart.core.http, { jobs: JOBS });
({ component, exists, table } = setup()); ({ component, exists, table } = setup());
@ -45,6 +41,10 @@ describe('<JobList />', () => {
component.update(); component.update();
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
});
test('should open the detail panel when clicking on a job in the table', () => { test('should open the detail panel when clicking on a job in the table', () => {
const { rows } = table.getMetaData('rollupJobsListTable'); const { rows } = table.getMetaData('rollupJobsListTable');
const button = rows[0].columns[1].reactWrapper.find('button'); const button = rows[0].columns[1].reactWrapper.find('button');

View file

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { mockHttpRequest, pageHelpers, nextTick } from './helpers';
import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants';
import { getRouter } from '../../public/crud_app/services/routing'; import { getRouter } from '../../public/crud_app/services/routing';
import { setHttp } from '../../public/crud_app/services';
import { CRUD_APP_BASE_PATH } from '../../public/crud_app/constants'; import { CRUD_APP_BASE_PATH } from '../../public/crud_app/constants';
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
@ -16,24 +17,22 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobList; const { setup } = pageHelpers.jobList;
describe('Smoke test cloning an existing rollup job from job list', () => { describe('Smoke test cloning an existing rollup job from job list', () => {
let server;
let httpRequestsMockHelpers;
let table; let table;
let find; let find;
let component; let component;
let exists; let exists;
let npStart;
beforeAll(() => { beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment()); npStart = require('ui/new_platform').npStart; // eslint-disable-line
}); setHttp(npStart.core.http);
afterAll(() => {
server.restore();
}); });
beforeEach(async () => { beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse(JOB_CLONE_INDEX_PATTERN_CHECK); mockHttpRequest(npStart.core.http, {
httpRequestsMockHelpers.setLoadJobsResponse(JOB_TO_CLONE); jobs: JOB_TO_CLONE,
indxPatternVldtResp: JOB_CLONE_INDEX_PATTERN_CHECK,
});
({ find, exists, table, component } = setup()); ({ find, exists, table, component } = setup());
@ -41,6 +40,10 @@ describe('Smoke test cloning an existing rollup job from job list', () => {
component.update(); component.update();
}); });
afterEach(() => {
npStart.core.http.get.mockClear();
});
it('should navigate to create view with default values set', async () => { it('should navigate to create view with default values set', async () => {
const router = getRouter(); const router = getRouter();
const { rows } = table.getMetaData('rollupJobsListTable'); const { rows } = table.getMetaData('rollupJobsListTable');

View file

@ -26,7 +26,7 @@ export function rollup(kibana) {
require: ['kibana', 'elasticsearch', 'xpack_main'], require: ['kibana', 'elasticsearch', 'xpack_main'],
uiExports: { uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'), styleSheetPaths: resolve(__dirname, 'public/index.scss'),
managementSections: ['plugins/rollup/crud_app'], managementSections: ['plugins/rollup/legacy'],
uiSettingDefaults: { uiSettingDefaults: {
[CONFIG_ROLLUPS]: { [CONFIG_ROLLUPS]: {
name: i18n.translate('xpack.rollupJobs.rollupIndexPatternsTitle', { name: i18n.translate('xpack.rollupJobs.rollupIndexPatternsTitle', {
@ -41,13 +41,9 @@ export function rollup(kibana) {
category: ['rollups'], category: ['rollups'],
}, },
}, },
indexManagement: [ indexManagement: ['plugins/rollup/legacy'],
'plugins/rollup/index_pattern_creation', visualize: ['plugins/rollup/legacy'],
'plugins/rollup/index_pattern_list', search: ['plugins/rollup/legacy'],
'plugins/rollup/extend_index_management',
],
visualize: ['plugins/rollup/visualize'],
search: ['plugins/rollup/search'],
}, },
init: function(server) { init: function(server) {
const { usageCollection } = server.newPlatform.setup.plugins; const { usageCollection } = server.newPlatform.setup.plugins;

View file

@ -0,0 +1,47 @@
/*
* 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 { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { ChromeBreadcrumb, CoreSetup } from '../../../../../src/core/public';
// @ts-ignore
import { rollupJobsStore } from './crud_app/store';
// @ts-ignore
import { App } from './crud_app/app';
/**
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
*/
export const renderApp = async (
core: CoreSetup,
{
element,
setBreadcrumbs,
}: { element: HTMLElement; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void }
) => {
const [coreStart] = await core.getStartServices();
const I18nContext = coreStart.i18n.Context;
render(
<I18nContext>
<KibanaContextProvider
services={{
setBreadcrumbs,
}}
>
<Provider store={rollupJobsStore}>
<App />
</Provider>
</KibanaContextProvider>
</I18nContext>,
element
);
return () => {
unmountComponentAtNode(element);
};
};

View file

@ -5,10 +5,6 @@
align-items: flex-end; /* 1 */ align-items: flex-end; /* 1 */
} }
.rollupJobsRoot {
display: flex;
}
/** /**
* 1. Ensure panel fills width of parent when search input yields no matching rollup jobs. * 1. Ensure panel fills width of parent when search input yields no matching rollup jobs.
*/ */

View file

@ -1,98 +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 from 'react';
import {
FeatureCatalogueRegistryProvider,
FeatureCatalogueCategory,
} from 'ui/registry/feature_catalogue';
import { render, unmountComponentAtNode } from 'react-dom';
import { Provider } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { I18nContext } from 'ui/i18n';
import { management } from 'ui/management';
import routes from 'ui/routes';
import { CRUD_APP_BASE_PATH } from './constants';
import { setHttp, setUserHasLeftApp } 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: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }),
order: 3,
url: `#${CRUD_APP_BASE_PATH}/job_list`,
});
const renderReact = async elem => {
render(
<I18nContext>
<Provider store={rollupJobsStore}>
<App />
</Provider>
</I18nContext>,
elem
);
};
routes.when(`${CRUD_APP_BASE_PATH}/:view?`, {
template: template,
controllerAs: 'rollupJobs',
controller: class IndexRollupJobsController {
constructor($scope, $route, $injector) {
// NOTE: We depend upon Angular's $http service because it's decorated with interceptors,
// e.g. to check license status per request.
setHttp($injector.get('$http'));
// If returning to the app, we'll need to reset this state.
setUserHasLeftApp(false);
$scope.$$postDigest(() => {
const appElement = document.getElementById('rollupJobsReactRoot');
renderReact(appElement);
const appRoute = $route.current;
const stopListeningForLocationChange = $scope.$on('$locationChangeSuccess', () => {
const currentRoute = $route.current;
const isNavigationInApp = currentRoute.$$route.template === appRoute.$$route.template;
// When we navigate within rollups, prevent Angular from re-matching the route and
// rebuilding the app.
if (isNavigationInApp) {
$route.current = appRoute;
} else {
// Set internal flag so we can prevent reacting to the route change internally.
setUserHasLeftApp(true);
}
});
$scope.$on('$destroy', () => {
stopListeningForLocationChange();
unmountComponentAtNode(appElement);
});
});
}
},
});
FeatureCatalogueRegistryProvider.register(() => {
return {
id: 'rollup_jobs',
title: 'Rollups',
description: i18n.translate('xpack.rollupJobs.featureCatalogueDescription', {
defaultMessage: 'Summarize and store historical data in a smaller index for future analysis.',
}),
icon: 'indexRollupApp',
path: `#${CRUD_APP_BASE_PATH}/job_list`,
showOnHomePage: true,
category: FeatureCatalogueCategory.ADMIN,
};
});

View file

@ -1,3 +0,0 @@
<kbn-management-app section="elasticsearch/rollup_jobs">
<div id="rollupJobsReactRoot" class="rollupJobsRoot"></div>
</kbn-management-app>

View file

@ -6,11 +6,12 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
class ConfirmDeleteModalUi extends Component { export class ConfirmDeleteModal extends Component {
static propTypes = { static propTypes = {
isSingleSelection: PropTypes.bool.isRequired, isSingleSelection: PropTypes.bool.isRequired,
jobs: PropTypes.array.isRequired, jobs: PropTypes.array.isRequired,
@ -19,12 +20,14 @@ class ConfirmDeleteModalUi extends Component {
}; };
renderJobs() { renderJobs() {
const { jobs, intl } = this.props; const { jobs } = this.props;
const jobItems = jobs.map(({ id, status }) => { const jobItems = jobs.map(({ id, status }) => {
const startedMessage = intl.formatMessage({ const startedMessage = i18n.translate(
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.startedMessage', 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.startedMessage',
defaultMessage: 'started', {
}); defaultMessage: 'started',
}
);
const statusText = status === 'started' ? ` (${startedMessage})` : null; const statusText = status === 'started' ? ` (${startedMessage})` : null;
return ( return (
<li key={id}> <li key={id}>
@ -38,19 +41,19 @@ class ConfirmDeleteModalUi extends Component {
} }
render() { render() {
const { isSingleSelection, jobs, onCancel, onConfirm, intl } = this.props; const { isSingleSelection, jobs, onCancel, onConfirm } = this.props;
let title; let title;
let content; let content;
if (isSingleSelection) { if (isSingleSelection) {
const { id, status } = jobs[0]; const { id, status } = jobs[0];
title = intl.formatMessage( title = i18n.translate(
'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.deleteSingleJobTitle',
{ {
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.deleteSingleJobTitle',
defaultMessage: "Delete rollup job '{id}'?", defaultMessage: "Delete rollup job '{id}'?",
}, values: { id },
{ id } }
); );
if (status === 'started') { if (status === 'started') {
@ -64,12 +67,12 @@ class ConfirmDeleteModalUi extends Component {
); );
} }
} else { } else {
title = intl.formatMessage( title = i18n.translate(
'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.multipleDeletionTitle',
{ {
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.multipleDeletionTitle',
defaultMessage: 'Delete {count} rollup jobs?', defaultMessage: 'Delete {count} rollup jobs?',
}, values: { count: jobs.length },
{ count: jobs.length } }
); );
content = ( content = (
@ -92,15 +95,19 @@ class ConfirmDeleteModalUi extends Component {
title={title} title={title}
onCancel={onCancel} onCancel={onCancel}
onConfirm={onConfirm} onConfirm={onConfirm}
cancelButtonText={intl.formatMessage({ cancelButtonText={i18n.translate(
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.cancelButtonText', 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.cancelButtonText',
defaultMessage: 'Cancel', {
})} defaultMessage: 'Cancel',
}
)}
buttonColor="danger" buttonColor="danger"
confirmButtonText={intl.formatMessage({ confirmButtonText={i18n.translate(
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.confirmButtonText', 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.confirmButtonText',
defaultMessage: 'Delete', {
})} defaultMessage: 'Delete',
}
)}
> >
{content} {content}
</EuiConfirmModal> </EuiConfirmModal>
@ -108,5 +115,3 @@ class ConfirmDeleteModalUi extends Component {
); );
} }
} }
export const ConfirmDeleteModal = injectI18n(ConfirmDeleteModalUi);

View file

@ -12,9 +12,9 @@ import debounce from 'lodash/function/debounce';
import first from 'lodash/array/first'; import first from 'lodash/array/first';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management'; import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/';
import { import {
EuiCallOut, EuiCallOut,
@ -27,8 +27,6 @@ import {
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { fatalError } from 'ui/notify';
import { import {
validateIndexPattern, validateIndexPattern,
formatFields, formatFields,
@ -59,6 +57,8 @@ import {
hasErrors, hasErrors,
} from './steps_config'; } from './steps_config';
import { getFatalErrors } from '../../../kibana_services';
const stepIdToTitleMap = { const stepIdToTitleMap = {
[STEP_LOGISTICS]: i18n.translate('xpack.rollupJobs.create.steps.stepLogisticsTitle', { [STEP_LOGISTICS]: i18n.translate('xpack.rollupJobs.create.steps.stepLogisticsTitle', {
defaultMessage: 'Logistics', defaultMessage: 'Logistics',
@ -92,7 +92,7 @@ export class JobCreateUi extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb, createBreadcrumb]); props.kibana.services.setBreadcrumbs([listBreadcrumb, createBreadcrumb]);
const { jobToClone: stepDefaultOverrides } = props; const { jobToClone: stepDefaultOverrides } = props;
const stepsFields = mapValues(stepIdToStepConfigMap, step => const stepsFields = mapValues(stepIdToStepConfigMap, step =>
cloneDeep(step.getDefaultFields(stepDefaultOverrides)) cloneDeep(step.getDefaultFields(stepDefaultOverrides))
@ -181,7 +181,7 @@ export class JobCreateUi extends Component {
dateFields: indexPatternDateFields, dateFields: indexPatternDateFields,
numericFields, numericFields,
keywordFields, keywordFields,
} = response.data; } = response;
let indexPatternAsyncErrors; let indexPatternAsyncErrors;
@ -298,9 +298,9 @@ export class JobCreateUi extends Component {
return; return;
} }
// Expect an error in the shape provided by Angular's $http service. // Expect an error in the shape provided by http service.
if (error && error.data) { if (error && error.body) {
const { error: errorString, statusCode } = error.data; const { error: errorString, statusCode } = error.body;
const indexPatternAsyncErrors = [ const indexPatternAsyncErrors = [
<FormattedMessage <FormattedMessage
@ -324,7 +324,7 @@ export class JobCreateUi extends Component {
// This error isn't an HTTP error, so let the fatal error screen tell the user something // This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened. // unexpected happened.
fatalError( getFatalErrors().add(
error, error,
i18n.translate('xpack.rollupJobs.create.errors.indexPatternValidationFatalErrorTitle', { i18n.translate('xpack.rollupJobs.create.errors.indexPatternValidationFatalErrorTitle', {
defaultMessage: 'Rollup Job Wizard index pattern validation', defaultMessage: 'Rollup Job Wizard index pattern validation',
@ -689,4 +689,4 @@ export class JobCreateUi extends Component {
} }
} }
export const JobCreate = injectI18n(JobCreateUi); export const JobCreate = withKibana(JobCreateUi);

View file

@ -6,7 +6,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiButton, EuiButton,
@ -18,7 +18,7 @@ import {
EuiCheckbox, EuiCheckbox,
} from '@elastic/eui'; } from '@elastic/eui';
const NavigationUi = ({ export const Navigation = ({
isSaving, isSaving,
hasNextStep, hasNextStep,
hasPreviousStep, hasPreviousStep,
@ -120,7 +120,7 @@ const NavigationUi = ({
); );
}; };
NavigationUi.propTypes = { Navigation.propTypes = {
hasNextStep: PropTypes.bool.isRequired, hasNextStep: PropTypes.bool.isRequired,
hasPreviousStep: PropTypes.bool.isRequired, hasPreviousStep: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
@ -129,5 +129,3 @@ NavigationUi.propTypes = {
save: PropTypes.func.isRequired, save: PropTypes.func.isRequired,
canGoToNextStep: PropTypes.bool.isRequired, canGoToNextStep: PropTypes.bool.isRequired,
}; };
export const Navigation = injectI18n(NavigationUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { import {
@ -26,7 +26,7 @@ import {
import { parseEsInterval } from '../../../../../../../../../src/legacy/core_plugins/data/public'; import { parseEsInterval } from '../../../../../../../../../src/legacy/core_plugins/data/public';
import { dateHistogramDetailsUrl, dateHistogramAggregationUrl } from '../../../services'; import { getDateHistogramDetailsUrl, getDateHistogramAggregationUrl } from '../../../services';
import { StepError } from './components'; import { StepError } from './components';
@ -35,7 +35,7 @@ const timeZoneOptions = moment.tz.names().map(name => ({
text: name, text: name,
})); }));
export class StepDateHistogramUi extends Component { export class StepDateHistogram extends Component {
static propTypes = { static propTypes = {
fields: PropTypes.object.isRequired, fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired, onFieldsChange: PropTypes.func.isRequired,
@ -192,7 +192,7 @@ export class StepDateHistogramUi extends Component {
<EuiButtonEmpty <EuiButtonEmpty
size="s" size="s"
flush="right" flush="right"
href={dateHistogramDetailsUrl} href={getDateHistogramDetailsUrl()}
target="_blank" target="_blank"
iconType="help" iconType="help"
data-test-subj="rollupJobCreateDateHistogramDocsButton" data-test-subj="rollupJobCreateDateHistogramDocsButton"
@ -218,7 +218,7 @@ export class StepDateHistogramUi extends Component {
defaultMessage="Define how {link} will operate on your rollup data." defaultMessage="Define how {link} will operate on your rollup data."
values={{ values={{
link: ( link: (
<EuiLink href={dateHistogramAggregationUrl} target="_blank"> <EuiLink href={getDateHistogramAggregationUrl()} target="_blank">
<FormattedMessage <FormattedMessage
id="xpack.rollupJobs.create.stepDateHistogramDescription.aggregationsLinkLabel" id="xpack.rollupJobs.create.stepDateHistogramDescription.aggregationsLinkLabel"
defaultMessage="date histogram aggregations" defaultMessage="date histogram aggregations"
@ -318,5 +318,3 @@ export class StepDateHistogramUi extends Component {
return <StepError />; return <StepError />;
}; };
} }
export const StepDateHistogram = injectI18n(StepDateHistogramUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiButtonEmpty, EuiButtonEmpty,
@ -20,13 +20,13 @@ import {
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { histogramDetailsUrl } from '../../../services'; import { getHistogramDetailsUrl } from '../../../services';
import { FieldList } from '../../components'; import { FieldList } from '../../components';
import { FieldChooser, StepError } from './components'; import { FieldChooser, StepError } from './components';
export class StepHistogramUi extends Component { export class StepHistogram extends Component {
static propTypes = { static propTypes = {
fields: PropTypes.object.isRequired, fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired, onFieldsChange: PropTypes.func.isRequired,
@ -96,7 +96,7 @@ export class StepHistogramUi extends Component {
<EuiButtonEmpty <EuiButtonEmpty
size="s" size="s"
flush="right" flush="right"
href={histogramDetailsUrl} href={getHistogramDetailsUrl()}
target="_blank" target="_blank"
iconType="help" iconType="help"
data-test-subj="rollupJobCreateHistogramDocsButton" data-test-subj="rollupJobCreateHistogramDocsButton"
@ -212,5 +212,3 @@ export class StepHistogramUi extends Component {
return <StepError />; return <StepError />;
}; };
} }
export const StepHistogram = injectI18n(StepHistogramUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiButtonEmpty, EuiButtonEmpty,
@ -26,8 +26,8 @@ import {
// eslint-disable-next-line @kbn/eslint/no-restricted-paths // eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor';
import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/indices'; import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../legacy_imports';
import { logisticalDetailsUrl, cronUrl } from '../../../services'; import { getLogisticalDetailsUrl, getCronUrl } from '../../../services';
import { StepError } from './components'; import { StepError } from './components';
import { indexPatterns } from '../../../../../../../../../src/plugins/data/public'; import { indexPatterns } from '../../../../../../../../../src/plugins/data/public';
@ -35,7 +35,7 @@ import { indexPatterns } from '../../../../../../../../../src/plugins/data/publi
const indexPatternIllegalCharacters = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.join(' '); const indexPatternIllegalCharacters = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.join(' ');
const indexIllegalCharacters = INDEX_ILLEGAL_CHARACTERS_VISIBLE.join(' '); const indexIllegalCharacters = INDEX_ILLEGAL_CHARACTERS_VISIBLE.join(' ');
export class StepLogisticsUi extends Component { export class StepLogistics extends Component {
static propTypes = { static propTypes = {
fields: PropTypes.object.isRequired, fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired, onFieldsChange: PropTypes.func.isRequired,
@ -146,7 +146,7 @@ export class StepLogisticsUi extends Component {
isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)} isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)}
helpText={ helpText={
<p> <p>
<EuiLink href={cronUrl} target="_blank"> <EuiLink href={getCronUrl()} target="_blank">
<FormattedMessage <FormattedMessage
id="xpack.rollupJobs.create.stepLogistics.fieldCron.helpReferenceLinkLabel" id="xpack.rollupJobs.create.stepLogistics.fieldCron.helpReferenceLinkLabel"
defaultMessage="Learn more about cron expressions" defaultMessage="Learn more about cron expressions"
@ -258,7 +258,7 @@ export class StepLogisticsUi extends Component {
<EuiButtonEmpty <EuiButtonEmpty
size="s" size="s"
flush="right" flush="right"
href={logisticalDetailsUrl} href={getLogisticalDetailsUrl()}
target="_blank" target="_blank"
iconType="help" iconType="help"
data-test-subj="rollupJobCreateLogisticsDocsButton" data-test-subj="rollupJobCreateLogisticsDocsButton"
@ -522,5 +522,3 @@ export class StepLogisticsUi extends Component {
return <StepError />; return <StepError />;
}; };
} }
export const StepLogistics = injectI18n(StepLogisticsUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import get from 'lodash/object/get'; import get from 'lodash/object/get';
@ -22,7 +22,7 @@ import {
EuiButton, EuiButton,
} from '@elastic/eui'; } from '@elastic/eui';
import { metricsDetailsUrl } from '../../../services'; import { getMetricsDetailsUrl } from '../../../services';
import { FieldList } from '../../components'; import { FieldList } from '../../components';
import { FieldChooser, StepError } from './components'; import { FieldChooser, StepError } from './components';
import { METRICS_CONFIG } from '../../../constants'; import { METRICS_CONFIG } from '../../../constants';
@ -64,7 +64,7 @@ const metricTypesConfig = (function() {
}); });
})(); })();
export class StepMetricsUi extends Component { export class StepMetrics extends Component {
static propTypes = { static propTypes = {
fields: PropTypes.object.isRequired, fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired, onFieldsChange: PropTypes.func.isRequired,
@ -247,7 +247,7 @@ export class StepMetricsUi extends Component {
} }
getListColumns() { getListColumns() {
return StepMetricsUi.chooserColumns.concat({ return StepMetrics.chooserColumns.concat({
type: 'metrics', type: 'metrics',
name: i18n.translate('xpack.rollupJobs.create.stepMetrics.metricsColumnHeader', { name: i18n.translate('xpack.rollupJobs.create.stepMetrics.metricsColumnHeader', {
defaultMessage: 'Metrics', defaultMessage: 'Metrics',
@ -384,7 +384,7 @@ export class StepMetricsUi extends Component {
<EuiButtonEmpty <EuiButtonEmpty
size="s" size="s"
flush="right" flush="right"
href={metricsDetailsUrl} href={getMetricsDetailsUrl()}
target="_blank" target="_blank"
iconType="help" iconType="help"
data-test-subj="rollupJobCreateMetricsDocsButton" data-test-subj="rollupJobCreateMetricsDocsButton"
@ -421,7 +421,7 @@ export class StepMetricsUi extends Component {
defaultMessage="Add metrics fields" defaultMessage="Add metrics fields"
/> />
} }
columns={StepMetricsUi.chooserColumns} columns={StepMetrics.chooserColumns}
fields={metricsFields} fields={metricsFields}
selectedFields={metrics} selectedFields={metrics}
onSelectField={this.onSelectField} onSelectField={this.onSelectField}
@ -472,5 +472,3 @@ export class StepMetricsUi extends Component {
}, },
]; ];
} }
export const StepMetrics = injectI18n(StepMetricsUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { EuiErrorBoundary, EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; import { EuiErrorBoundary, EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui';
@ -30,7 +30,7 @@ const JOB_DETAILS_TABS = [
JOB_DETAILS_TAB_REQUEST, JOB_DETAILS_TAB_REQUEST,
]; ];
export class StepReviewUi extends Component { export class StepReview extends Component {
static propTypes = { static propTypes = {
job: PropTypes.object.isRequired, job: PropTypes.object.isRequired,
}; };
@ -121,5 +121,3 @@ export class StepReviewUi extends Component {
); );
} }
} }
export const StepReview = injectI18n(StepReviewUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiButtonEmpty, EuiButtonEmpty,
@ -17,13 +17,13 @@ import {
EuiTitle, EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { termsDetailsUrl } from '../../../services'; import { getTermsDetailsUrl } from '../../../services';
import { FieldList } from '../../components'; import { FieldList } from '../../components';
import { FieldChooser } from './components'; import { FieldChooser } from './components';
export class StepTermsUi extends Component { export class StepTerms extends Component {
static propTypes = { static propTypes = {
fields: PropTypes.object.isRequired, fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired, onFieldsChange: PropTypes.func.isRequired,
@ -99,7 +99,7 @@ export class StepTermsUi extends Component {
<EuiButtonEmpty <EuiButtonEmpty
size="s" size="s"
flush="right" flush="right"
href={termsDetailsUrl} href={getTermsDetailsUrl()}
target="_blank" target="_blank"
iconType="help" iconType="help"
data-test-subj="rollupJobCreateTermsDocsButton" data-test-subj="rollupJobCreateTermsDocsButton"
@ -140,5 +140,3 @@ export class StepTermsUi extends Component {
); );
} }
} }
export const StepTerms = injectI18n(StepTermsUi);

View file

@ -6,7 +6,7 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { findIllegalCharactersInIndexName } from 'ui/indices'; import { findIllegalCharactersInIndexName } from '../../../../legacy_imports';
export function validateRollupIndex(rollupIndex, indexPattern) { export function validateRollupIndex(rollupIndex, indexPattern) {
if (!rollupIndex || !rollupIndex.trim()) { if (!rollupIndex || !rollupIndex.trim()) {

View file

@ -6,7 +6,8 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiErrorBoundary, EuiErrorBoundary,
@ -62,7 +63,7 @@ const tabToUiMetricMap = {
[JOB_DETAILS_TAB_JSON]: UIM_DETAIL_PANEL_JSON_TAB_CLICK, [JOB_DETAILS_TAB_JSON]: UIM_DETAIL_PANEL_JSON_TAB_CLICK,
}; };
export class DetailPanelUi extends Component { export class DetailPanel extends Component {
static propTypes = { static propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -130,7 +131,7 @@ export class DetailPanelUi extends Component {
} }
renderJob() { renderJob() {
const { panelType, job, intl } = this.props; const { panelType, job } = this.props;
const { status, documentsProcessed, pagesProcessed, rollupsIndexed, triggerCount, json } = job; const { status, documentsProcessed, pagesProcessed, rollupsIndexed, triggerCount, json } = job;
@ -159,8 +160,7 @@ export class DetailPanelUi extends Component {
anchorPosition="upRight" anchorPosition="upRight"
detailPanel={true} detailPanel={true}
iconType="arrowUp" iconType="arrowUp"
label={intl.formatMessage({ label={i18n.translate('xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel', {
id: 'xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel',
defaultMessage: 'Manage', defaultMessage: 'Manage',
})} })}
/> />
@ -251,5 +251,3 @@ export class DetailPanelUi extends Component {
); );
} }
} }
export const DetailPanel = injectI18n(DetailPanelUi);

View file

@ -58,12 +58,12 @@ describe('<DetailPanel />', () => {
}); });
it("should have children if it's open", () => { it("should have children if it's open", () => {
expect(component.find('DetailPanelUi').children().length).toBeTruthy(); expect(component.find('DetailPanel').children().length).toBeTruthy();
}); });
it('should *not* have children if its closed', () => { it('should *not* have children if its closed', () => {
({ component } = initTestBed({ isOpen: false })); ({ component } = initTestBed({ isOpen: false }));
expect(component.find('DetailPanelUi').children().length).toBeFalsy(); expect(component.find('DetailPanel').children().length).toBeFalsy();
}); });
it('should show a loading when the job is loading', () => { it('should show a loading when the job is loading', () => {

View file

@ -6,9 +6,8 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
import { import {
EuiButton, EuiButton,
@ -26,6 +25,8 @@ import {
EuiCallOut, EuiCallOut,
} from '@elastic/eui'; } from '@elastic/eui';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/';
import { CRUD_APP_BASE_PATH } from '../../constants'; import { CRUD_APP_BASE_PATH } from '../../constants';
import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services'; import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services';
@ -67,7 +68,7 @@ export class JobListUi extends Component {
props.loadJobs(); props.loadJobs();
chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb]); props.kibana.services.setBreadcrumbs([listBreadcrumb]);
this.state = {}; this.state = {};
} }
@ -97,9 +98,7 @@ export class JobListUi extends Component {
} }
renderNoPermission() { renderNoPermission() {
const { intl } = this.props; const title = i18n.translate('xpack.rollupJobs.jobList.noPermissionTitle', {
const title = intl.formatMessage({
id: 'xpack.rollupJobs.jobList.noPermissionTitle',
defaultMessage: 'Permission error', defaultMessage: 'Permission error',
}); });
return ( return (
@ -122,13 +121,11 @@ export class JobListUi extends Component {
} }
renderError(error) { renderError(error) {
// We can safely depend upon the shape of this error coming from Angular $http, because we // We can safely depend upon the shape of this error coming from http service, because we
// handle unexpected error shapes in the API action. // handle unexpected error shapes in the API action.
const { statusCode, error: errorString } = error.data; const { statusCode, error: errorString } = error.body;
const { intl } = this.props; const title = i18n.translate('xpack.rollupJobs.jobList.loadingErrorTitle', {
const title = intl.formatMessage({
id: 'xpack.rollupJobs.jobList.loadingErrorTitle',
defaultMessage: 'Error loading rollup jobs', defaultMessage: 'Error loading rollup jobs',
}); });
return ( return (
@ -254,4 +251,4 @@ export class JobListUi extends Component {
} }
} }
export const JobList = injectI18n(JobListUi); export const JobList = withKibana(JobListUi);

View file

@ -4,24 +4,16 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import React from 'react';
import { registerTestBed } from '../../../../../../../test_utils'; import { registerTestBed } from '../../../../../../../test_utils';
import { rollupJobsStore } from '../../store'; import { rollupJobsStore } from '../../store';
import { JobList } from './job_list'; import { JobList } from './job_list';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { coreMock } from '../../../../../../../../src/core/public/mocks';
const startMock = coreMock.createStart();
jest.mock('ui/new_platform'); jest.mock('ui/new_platform');
jest.mock('ui/chrome', () => ({
addBasePath: () => {},
breadcrumbs: { set: () => {} },
getInjected: key => {
if (key === 'uiCapabilities') {
return {
navLinks: {},
management: {},
catalogue: {},
};
}
},
}));
jest.mock('../../services', () => { jest.mock('../../services', () => {
const services = require.requireActual('../../services'); const services = require.requireActual('../../services');
@ -40,7 +32,16 @@ const defaultProps = {
isLoading: false, isLoading: false,
}; };
const initTestBed = registerTestBed(JobList, { defaultProps, store: rollupJobsStore }); const services = {
setBreadcrumbs: startMock.chrome.setBreadcrumbs,
};
const Component = props => (
<KibanaContextProvider services={services}>
<JobList {...props} />
</KibanaContextProvider>
);
const initTestBed = registerTestBed(Component, { defaultProps, store: rollupJobsStore });
describe('<JobList />', () => { describe('<JobList />', () => {
it('should render empty prompt when loading is complete and there are no jobs', () => { it('should render empty prompt when loading is complete and there are no jobs', () => {
@ -53,21 +54,21 @@ describe('<JobList />', () => {
const { component, exists } = initTestBed({ isLoading: true }); const { component, exists } = initTestBed({ isLoading: true });
expect(exists('jobListLoading')).toBeTruthy(); expect(exists('jobListLoading')).toBeTruthy();
expect(component.find('JobTableUi').length).toBeFalsy(); expect(component.find('JobTable').length).toBeFalsy();
}); });
it('should display the <JobTable /> when there are jobs', () => { it('should display the <JobTable /> when there are jobs', () => {
const { component, exists } = initTestBed({ hasJobs: true }); const { component, exists } = initTestBed({ hasJobs: true });
expect(exists('jobListLoading')).toBeFalsy(); expect(exists('jobListLoading')).toBeFalsy();
expect(component.find('JobTableUi').length).toBeTruthy(); expect(component.find('JobTable').length).toBeTruthy();
}); });
describe('when there is an API error', () => { describe('when there is an API error', () => {
const { exists, find } = initTestBed({ const { exists, find } = initTestBed({
jobLoadError: { jobLoadError: {
status: 400, status: 400,
data: { statusCode: 400, error: 'Houston we got a problem.' }, body: { statusCode: 400, error: 'Houston we got a problem.' },
}, },
}); });

View file

@ -7,7 +7,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiCheckbox, EuiCheckbox,
@ -120,7 +120,7 @@ const COLUMNS = [
}, },
]; ];
export class JobTableUi extends Component { export class JobTable extends Component {
static propTypes = { static propTypes = {
jobs: PropTypes.array, jobs: PropTypes.array,
pager: PropTypes.object.isRequired, pager: PropTypes.object.isRequired,
@ -333,7 +333,7 @@ export class JobTableUi extends Component {
} }
render() { render() {
const { filterChanged, filter, jobs, intl, closeDetailPanel } = this.props; const { filterChanged, filter, jobs, closeDetailPanel } = this.props;
const { idToSelectedJobMap } = this.state; const { idToSelectedJobMap } = this.state;
@ -360,8 +360,7 @@ export class JobTableUi extends Component {
filterChanged(event.target.value); filterChanged(event.target.value);
}} }}
data-test-subj="jobTableFilterInput" data-test-subj="jobTableFilterInput"
placeholder={intl.formatMessage({ placeholder={i18n.translate('xpack.rollupJobs.jobTable.searchInputPlaceholder', {
id: 'xpack.rollupJobs.jobTable.searchInputPlaceholder',
defaultMessage: 'Search', defaultMessage: 'Search',
})} })}
aria-label="Search jobs" aria-label="Search jobs"
@ -405,5 +404,3 @@ export class JobTableUi extends Component {
); );
} }
} }
export const JobTable = injectI18n(JobTableUi);

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import chrome from 'ui/chrome';
import { import {
UIM_JOB_CREATE, UIM_JOB_CREATE,
UIM_JOB_DELETE, UIM_JOB_DELETE,
@ -17,39 +16,45 @@ import {
import { getHttp } from './http_provider'; import { getHttp } from './http_provider';
import { trackUserRequest } from './track_ui_metric'; import { trackUserRequest } from './track_ui_metric';
const apiPrefix = chrome.addBasePath('/api/rollup'); const apiPrefix = '/api/rollup';
export async function loadJobs() { export async function loadJobs() {
const { const { jobs } = await getHttp().get(`${apiPrefix}/jobs`);
data: { jobs },
} = await getHttp().get(`${apiPrefix}/jobs`);
return jobs; return jobs;
} }
export async function startJobs(jobIds) { export async function startJobs(jobIds) {
const body = { jobIds }; const body = { jobIds };
const request = getHttp().post(`${apiPrefix}/start`, body); const request = getHttp().post(`${apiPrefix}/start`, {
body: JSON.stringify(body),
});
const actionType = jobIds.length > 1 ? UIM_JOB_START_MANY : UIM_JOB_START; const actionType = jobIds.length > 1 ? UIM_JOB_START_MANY : UIM_JOB_START;
return await trackUserRequest(request, actionType); return await trackUserRequest(request, actionType);
} }
export async function stopJobs(jobIds) { export async function stopJobs(jobIds) {
const body = { jobIds }; const body = { jobIds };
const request = getHttp().post(`${apiPrefix}/stop`, body); const request = getHttp().post(`${apiPrefix}/stop`, {
body: JSON.stringify(body),
});
const actionType = jobIds.length > 1 ? UIM_JOB_STOP_MANY : UIM_JOB_STOP; const actionType = jobIds.length > 1 ? UIM_JOB_STOP_MANY : UIM_JOB_STOP;
return await trackUserRequest(request, actionType); return await trackUserRequest(request, actionType);
} }
export async function deleteJobs(jobIds) { export async function deleteJobs(jobIds) {
const body = { jobIds }; const body = { jobIds };
const request = getHttp().post(`${apiPrefix}/delete`, body); const request = getHttp().post(`${apiPrefix}/delete`, {
body: JSON.stringify(body),
});
const actionType = jobIds.length > 1 ? UIM_JOB_DELETE_MANY : UIM_JOB_DELETE; const actionType = jobIds.length > 1 ? UIM_JOB_DELETE_MANY : UIM_JOB_DELETE;
return await trackUserRequest(request, actionType); return await trackUserRequest(request, actionType);
} }
export async function createJob(job) { export async function createJob(job) {
const body = { job }; const body = { job };
const request = getHttp().put(`${apiPrefix}/create`, body); const request = getHttp().put(`${apiPrefix}/create`, {
body: JSON.stringify(body),
});
return await trackUserRequest(request, UIM_JOB_CREATE); return await trackUserRequest(request, UIM_JOB_CREATE);
} }

View file

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { fatalError, toastNotifications } from 'ui/notify'; import { getNotifications, getFatalErrors } from '../../kibana_services';
function createToastConfig(error, errorTitle) { function createToastConfig(error: any, errorTitle: string) {
// Expect an error in the shape provided by Angular's $http service. // Expect an error in the shape provided by http service.
if (error && error.data) { if (error && error.body) {
const { error: errorString, statusCode, message } = error.data; const { error: errorString, statusCode, message } = error.body;
return { return {
title: errorTitle, title: errorTitle,
text: `${statusCode}: ${errorString}. ${message}`, text: `${statusCode}: ${errorString}. ${message}`,
@ -17,26 +17,26 @@ function createToastConfig(error, errorTitle) {
} }
} }
export function showApiWarning(error, errorTitle) { export function showApiWarning(error: any, errorTitle: string) {
const toastConfig = createToastConfig(error, errorTitle); const toastConfig = createToastConfig(error, errorTitle);
if (toastConfig) { if (toastConfig) {
return toastNotifications.addWarning(toastConfig); return getNotifications().toasts.addWarning(toastConfig);
} }
// This error isn't an HTTP error, so let the fatal error screen tell the user something // This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened. // unexpected happened.
return fatalError(error, errorTitle); return getFatalErrors().add(error, errorTitle);
} }
export function showApiError(error, errorTitle) { export function showApiError(error: any, errorTitle: string) {
const toastConfig = createToastConfig(error, errorTitle); const toastConfig = createToastConfig(error, errorTitle);
if (toastConfig) { if (toastConfig) {
return toastNotifications.addDanger(toastConfig); return getNotifications().toasts.addDanger(toastConfig);
} }
// This error isn't an HTTP error, so let the fatal error screen tell the user something // This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened. // unexpected happened.
fatalError(error, errorTitle); getFatalErrors().add(error, errorTitle);
} }

View file

@ -4,16 +4,21 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; let esBase = '';
let xPackBase = '';
const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; export function setEsBaseAndXPackBase(elasticWebsiteUrl, docLinksVersion) {
const xPackBase = `${ELASTIC_WEBSITE_URL}guide/en/x-pack/${DOC_LINK_VERSION}`; esBase = `${elasticWebsiteUrl}guide/en/elasticsearch/reference/${docLinksVersion}`;
xPackBase = `${elasticWebsiteUrl}guide/en/x-pack/${docLinksVersion}`;
}
export const logisticalDetailsUrl = `${esBase}/rollup-job-config.html#_logistical_details`; export const getLogisticalDetailsUrl = () => `${esBase}/rollup-job-config.html#_logistical_details`;
export const dateHistogramDetailsUrl = `${esBase}/rollup-job-config.html#_date_histogram_2`; export const getDateHistogramDetailsUrl = () =>
export const termsDetailsUrl = `${esBase}/rollup-job-config.html#_terms_2`; `${esBase}/rollup-job-config.html#_date_histogram_2`;
export const histogramDetailsUrl = `${esBase}/rollup-job-config.html#_histogram_2`; export const getTermsDetailsUrl = () => `${esBase}/rollup-job-config.html#_terms_2`;
export const metricsDetailsUrl = `${esBase}/rollup-job-config.html#rollup-metrics-config`; export const getHistogramDetailsUrl = () => `${esBase}/rollup-job-config.html#_histogram_2`;
export const getMetricsDetailsUrl = () => `${esBase}/rollup-job-config.html#rollup-metrics-config`;
export const dateHistogramAggregationUrl = `${esBase}/search-aggregations-bucket-datehistogram-aggregation.html`; export const getDateHistogramAggregationUrl = () =>
export const cronUrl = `${xPackBase}/trigger-schedule.html#_cron_expressions`; `${esBase}/search-aggregations-bucket-datehistogram-aggregation.html`;
export const getCronUrl = () => `${xPackBase}/trigger-schedule.html#_cron_expressions`;

View file

@ -4,14 +4,17 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
// This is an Angular service, which is why we use this provider pattern to access it within import { HttpStart } from 'src/core/public';
// our React app.
let _http;
export function setHttp(http) { let _http: HttpStart | null = null;
export function setHttp(http: HttpStart) {
_http = http; _http = http;
} }
export function getHttp() { export function getHttp() {
if (!_http) {
throw new Error('Rollup http is not defined');
}
return _http; return _http;
} }

View file

@ -11,13 +11,14 @@ export { showApiError, showApiWarning } from './api_errors';
export { listBreadcrumb, createBreadcrumb } from './breadcrumbs'; export { listBreadcrumb, createBreadcrumb } from './breadcrumbs';
export { export {
logisticalDetailsUrl, setEsBaseAndXPackBase,
dateHistogramDetailsUrl, getLogisticalDetailsUrl,
dateHistogramAggregationUrl, getDateHistogramDetailsUrl,
termsDetailsUrl, getDateHistogramAggregationUrl,
histogramDetailsUrl, getTermsDetailsUrl,
metricsDetailsUrl, getHistogramDetailsUrl,
cronUrl, getMetricsDetailsUrl,
getCronUrl,
} from './documentation_links'; } from './documentation_links';
export { filterItems } from './filter_items'; export { filterItems } from './filter_items';

View file

@ -5,7 +5,6 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { fatalError } from 'ui/notify';
import { CRUD_APP_BASE_PATH } from '../../constants'; import { CRUD_APP_BASE_PATH } from '../../constants';
import { import {
@ -24,6 +23,8 @@ import {
CLEAR_CREATE_JOB_ERRORS, CLEAR_CREATE_JOB_ERRORS,
} from '../action_types'; } from '../action_types';
import { getFatalErrors } from '../../../kibana_services';
export const createJob = jobConfig => async dispatch => { export const createJob = jobConfig => async dispatch => {
dispatch({ dispatch({
type: CREATE_JOB_START, type: CREATE_JOB_START,
@ -39,12 +40,13 @@ export const createJob = jobConfig => async dispatch => {
]); ]);
} catch (error) { } catch (error) {
if (error) { if (error) {
const { statusCode, data } = error; const { body } = error;
const statusCode = error.statusCode || (body && body.statusCode);
// Expect an error in the shape provided by Angular's $http service. // Expect an error in the shape provided by http service.
if (data) { if (body) {
// Some errors have statusCode directly available but some are under a data property. // Some errors have statusCode directly available but some are under a data property.
if ((statusCode || (data && data.statusCode)) === 409) { if (statusCode === 409) {
return dispatch({ return dispatch({
type: CREATE_JOB_FAILURE, type: CREATE_JOB_FAILURE,
payload: { payload: {
@ -67,9 +69,9 @@ export const createJob = jobConfig => async dispatch => {
error: { error: {
message: i18n.translate('xpack.rollupJobs.createAction.failedDefaultErrorMessage', { message: i18n.translate('xpack.rollupJobs.createAction.failedDefaultErrorMessage', {
defaultMessage: 'Request failed with a {statusCode} error. {message}', defaultMessage: 'Request failed with a {statusCode} error. {message}',
values: { statusCode, message: data.message }, values: { statusCode, message: body.message },
}), }),
cause: data.cause, cause: body.cause,
}, },
}, },
}); });
@ -78,7 +80,7 @@ export const createJob = jobConfig => async dispatch => {
// This error isn't an HTTP error, so let the fatal error screen tell the user something // This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened. // unexpected happened.
return fatalError( return getFatalErrors().add(
error, error,
i18n.translate('xpack.rollupJobs.createAction.errorTitle', { i18n.translate('xpack.rollupJobs.createAction.errorTitle', {
defaultMessage: 'Error creating rollup job', defaultMessage: 'Error creating rollup job',
@ -86,7 +88,7 @@ export const createJob = jobConfig => async dispatch => {
); );
} }
const deserializedJob = deserializeJob(newJob.data); const deserializedJob = deserializeJob(newJob);
dispatch({ dispatch({
type: CREATE_JOB_SUCCESS, type: CREATE_JOB_SUCCESS,

View file

@ -5,7 +5,6 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { import {
deleteJobs as sendDeleteJobsRequest, deleteJobs as sendDeleteJobsRequest,
@ -19,6 +18,8 @@ import { UPDATE_JOB_START, UPDATE_JOB_SUCCESS, UPDATE_JOB_FAILURE } from '../act
import { refreshJobs } from './refresh_jobs'; import { refreshJobs } from './refresh_jobs';
import { closeDetailPanel } from './detail_panel'; import { closeDetailPanel } from './detail_panel';
import { getNotifications } from '../../../kibana_services';
export const deleteJobs = jobIds => async (dispatch, getState) => { export const deleteJobs = jobIds => async (dispatch, getState) => {
dispatch({ dispatch({
type: UPDATE_JOB_START, type: UPDATE_JOB_START,
@ -40,14 +41,14 @@ export const deleteJobs = jobIds => async (dispatch, getState) => {
} }
if (jobIds.length === 1) { if (jobIds.length === 1) {
toastNotifications.addSuccess( getNotifications().toasts.addSuccess(
i18n.translate('xpack.rollupJobs.deleteAction.successSingleNotificationTitle', { i18n.translate('xpack.rollupJobs.deleteAction.successSingleNotificationTitle', {
defaultMessage: `Rollup job '{jobId}' was deleted`, defaultMessage: `Rollup job '{jobId}' was deleted`,
values: { jobId: jobIds[0] }, values: { jobId: jobIds[0] },
}) })
); );
} else { } else {
toastNotifications.addSuccess( getNotifications().toasts.addSuccess(
i18n.translate('xpack.rollupJobs.deleteAction.successMultipleNotificationTitle', { i18n.translate('xpack.rollupJobs.deleteAction.successMultipleNotificationTitle', {
defaultMessage: '{count} rollup jobs were deleted', defaultMessage: '{count} rollup jobs were deleted',
values: { count: jobIds.length }, values: { count: jobIds.length },

View file

@ -4,15 +4,11 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import {
addToggleExtension,
addBadgeExtension,
} from '../../../index_management/public/index_management_extensions';
import { get } from 'lodash'; import { get } from 'lodash';
const propertyPath = 'isRollupIndex'; const propertyPath = 'isRollupIndex';
export const rollupToggleExtension = { export const rollupToggleExtension = {
matchIndex: index => { matchIndex: (index: { isRollupIndex: boolean }) => {
return get(index, propertyPath); return get(index, propertyPath);
}, },
label: i18n.translate('xpack.rollupJobs.indexMgmtToggle.toggleLabel', { label: i18n.translate('xpack.rollupJobs.indexMgmtToggle.toggleLabel', {
@ -20,8 +16,9 @@ export const rollupToggleExtension = {
}), }),
name: 'rollupToggle', name: 'rollupToggle',
}; };
export const rollupBadgeExtension = { export const rollupBadgeExtension = {
matchIndex: index => { matchIndex: (index: { isRollupIndex: boolean }) => {
return get(index, propertyPath); return get(index, propertyPath);
}, },
label: i18n.translate('xpack.rollupJobs.indexMgmtBadge.rollupLabel', { label: i18n.translate('xpack.rollupJobs.indexMgmtBadge.rollupLabel', {
@ -30,6 +27,3 @@ export const rollupBadgeExtension = {
color: 'secondary', color: 'secondary',
filterExpression: 'isRollupIndex:true', filterExpression: 'isRollupIndex:true',
}; };
addBadgeExtension(rollupBadgeExtension);
addToggleExtension(rollupToggleExtension);

View file

@ -1,17 +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 chrome from 'ui/chrome';
import { initIndexPatternCreation } from './register';
import { CONFIG_ROLLUPS } from '../../common';
const uiSettings = chrome.getUiSettingsClient();
const isRollupIndexPatternsEnabled = uiSettings.get(CONFIG_ROLLUPS);
if (isRollupIndexPatternsEnabled) {
initIndexPatternCreation();
}

View file

@ -1,12 +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 { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy';
import { RollupIndexPatternCreationConfig } from './rollup_index_pattern_creation_config';
export function initIndexPatternCreation() {
managementSetup.indexPattern.creation.add(RollupIndexPatternCreationConfig);
}

View file

@ -6,10 +6,8 @@
import React from 'react'; import React from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { npSetup } from 'ui/new_platform';
import { RollupPrompt } from './components/rollup_prompt'; import { RollupPrompt } from './components/rollup_prompt';
import { setHttpClient, getRollupIndices } from '../services/api';
import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public';
const rollupIndexPatternTypeName = i18n.translate( const rollupIndexPatternTypeName = i18n.translate(
@ -54,7 +52,6 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
...options, ...options,
}); });
setHttpClient(this.httpClient);
this.rollupIndex = null; this.rollupIndex = null;
this.rollupJobs = []; this.rollupJobs = [];
this.rollupIndicesCapabilities = {}; this.rollupIndicesCapabilities = {};
@ -67,9 +64,10 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
// This is a hack intended to prevent the getRollupIndices() request from being sent if // This is a hack intended to prevent the getRollupIndices() request from being sent if
// we're on /logout. There is a race condition that can arise on that page, whereby this // we're on /logout. There is a race condition that can arise on that page, whereby this
// request resolves after the logout request resolves, and un-clears the session ID. // request resolves after the logout request resolves, and un-clears the session ID.
const isAnonymous = npSetup.core.http.anonymousPaths.isAnonymous(window.location.pathname); const isAnonymous = this.httpClient.anonymousPaths.isAnonymous(window.location.pathname);
if (!isAnonymous) { if (!isAnonymous) {
this.rollupIndicesCapabilities = await getRollupIndices(); const response = await this.httpClient.get('/api/rollup/indices');
this.rollupIndicesCapabilities = response || {};
} }
this.rollupIndices = Object.keys(this.rollupIndicesCapabilities); this.rollupIndices = Object.keys(this.rollupIndicesCapabilities);

View file

@ -1,16 +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 chrome from 'ui/chrome';
import { initIndexPatternList } from './register';
import { CONFIG_ROLLUPS } from '../../common';
const uiSettings = chrome.getUiSettingsClient();
const isRollupIndexPatternsEnabled = uiSettings.get(CONFIG_ROLLUPS);
if (isRollupIndexPatternsEnabled) {
initIndexPatternList();
}

View file

@ -1,12 +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 { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy';
import { RollupIndexPatternListConfig } from './rollup_index_pattern_list_config';
export function initIndexPatternList() {
managementSetup.indexPattern.list.add(RollupIndexPatternListConfig);
}

View file

@ -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 { NotificationsStart, FatalErrorsSetup } from 'src/core/public';
let notifications: NotificationsStart | null = null;
let fatalErrors: FatalErrorsSetup | null = null;
export function getNotifications() {
if (!notifications) {
throw new Error('Rollup notifications is not defined');
}
return notifications;
}
export function setNotifications(newNotifications: NotificationsStart) {
notifications = newNotifications;
}
export function getFatalErrors() {
if (!fatalErrors) {
throw new Error('Rollup fatalErrors is not defined');
}
return fatalErrors;
}
export function setFatalErrors(newFatalErrors: FatalErrorsSetup) {
fatalErrors = newFatalErrors;
}

View file

@ -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 { npSetup, npStart } from 'ui/new_platform';
import { editorConfigProviders } from 'ui/vis/config';
import { aggTypeFilters } from 'ui/agg_types/filter';
import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter';
import { addSearchStrategy } from '../../../../../src/plugins/data/public';
import { RollupPlugin } from './plugin';
import { setup as management } from '../../../../../src/legacy/core_plugins/management/public/legacy';
import { addBadgeExtension, addToggleExtension } from '../../index_management/public';
const plugin = new RollupPlugin();
export const setup = plugin.setup(npSetup.core, {
...npSetup.plugins,
__LEGACY: {
aggTypeFilters,
aggTypeFieldFilters,
editorConfigProviders,
addSearchStrategy,
addBadgeExtension,
addToggleExtension,
managementLegacy: management,
},
});
export const start = plugin.start(npStart.core);

View file

@ -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.
*/
// @ts-ignore
export { findIllegalCharactersInIndexName, INDEX_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/indices';
export { AggTypeFilters } from 'ui/agg_types/filter';
export { AggTypeFieldFilters } from 'ui/agg_types/param_types/filter';
export { EditorConfigProviderRegistry } from 'ui/vis/config';

View file

@ -0,0 +1,128 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import {
EditorConfigProviderRegistry,
AggTypeFilters,
AggTypeFieldFilters,
} from './legacy_imports';
import { SearchStrategyProvider } from '../../../../../src/plugins/data/public';
import { ManagementSetup as ManagementSetupLegacy } from '../../../../../src/legacy/core_plugins/management/public/np_ready';
import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_management';
// @ts-ignore
import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config';
// @ts-ignore
import { RollupIndexPatternListConfig } from './index_pattern_list/rollup_index_pattern_list_config';
import { getRollupSearchStrategy } from './search/rollup_search_strategy';
// @ts-ignore
import { initAggTypeFilter } from './visualize/agg_type_filter';
// @ts-ignore
import { initAggTypeFieldFilter } from './visualize/agg_type_field_filter';
// @ts-ignore
import { initEditorConfig } from './visualize/editor_config';
import { CONFIG_ROLLUPS } from '../common';
import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../../src/plugins/home/public';
// @ts-ignore
import { CRUD_APP_BASE_PATH } from './crud_app/constants';
import { ManagementSetup } from '../../../../../src/plugins/management/public';
// @ts-ignore
import { setEsBaseAndXPackBase, setHttp } from './crud_app/services';
import { setNotifications, setFatalErrors } from './kibana_services';
import { renderApp } from './application';
export interface RollupPluginSetupDependencies {
__LEGACY: {
aggTypeFilters: AggTypeFilters;
aggTypeFieldFilters: AggTypeFieldFilters;
editorConfigProviders: EditorConfigProviderRegistry;
addSearchStrategy: (searchStrategy: SearchStrategyProvider) => void;
managementLegacy: ManagementSetupLegacy;
addBadgeExtension: (badgeExtension: any) => void;
addToggleExtension: (toggleExtension: any) => void;
};
home?: HomePublicPluginSetup;
management: ManagementSetup;
}
export class RollupPlugin implements Plugin {
setup(
core: CoreSetup,
{
__LEGACY: {
aggTypeFilters,
aggTypeFieldFilters,
editorConfigProviders,
addSearchStrategy,
managementLegacy,
addBadgeExtension,
addToggleExtension,
},
home,
management,
}: RollupPluginSetupDependencies
) {
setFatalErrors(core.fatalErrors);
addBadgeExtension(rollupBadgeExtension);
addToggleExtension(rollupToggleExtension);
const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS);
if (isRollupIndexPatternsEnabled) {
managementLegacy.indexPattern.creation.add(RollupIndexPatternCreationConfig);
managementLegacy.indexPattern.list.add(RollupIndexPatternListConfig);
addSearchStrategy(getRollupSearchStrategy(core.http.fetch));
initAggTypeFilter(aggTypeFilters);
initAggTypeFieldFilter(aggTypeFieldFilters);
initEditorConfig(editorConfigProviders);
}
if (home) {
home.featureCatalogue.register({
id: 'rollup_jobs',
title: 'Rollups',
description: i18n.translate('xpack.rollupJobs.featureCatalogueDescription', {
defaultMessage:
'Summarize and store historical data in a smaller index for future analysis.',
}),
icon: 'indexRollupApp',
path: `#${CRUD_APP_BASE_PATH}/job_list`,
showOnHomePage: true,
category: FeatureCatalogueCategory.ADMIN,
});
}
const esSection = management.sections.getSection('elasticsearch');
if (esSection) {
esSection.registerApp({
id: 'rollup_jobs',
title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }),
order: 3,
mount(params) {
params.setBreadcrumbs([
{
text: i18n.translate('xpack.rollupJobs.breadcrumbsTitle', {
defaultMessage: 'Rollup Jobs',
}),
},
]);
return renderApp(core, params);
},
});
}
}
start(core: CoreStart) {
setHttp(core.http);
setNotifications(core.notifications);
setEsBaseAndXPackBase(core.docLinks.ELASTIC_WEBSITE_URL, core.docLinks.DOC_LINK_VERSION);
}
}

View file

@ -1,17 +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 chrome from 'ui/chrome';
import { initSearch } from './register';
import { CONFIG_ROLLUPS } from '../../common';
const uiSettings = chrome.getUiSettingsClient();
const isRollupIndexPatternsEnabled = uiSettings.get(CONFIG_ROLLUPS);
if (isRollupIndexPatternsEnabled) {
initSearch();
}

View file

@ -1,12 +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 { addSearchStrategy } from '../../../../../../src/plugins/data/public';
import { rollupSearchStrategy } from './rollup_search_strategy';
export function initSearch() {
addSearchStrategy(rollupSearchStrategy);
}

View file

@ -4,10 +4,17 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { kfetch } from 'ui/kfetch'; import { HttpSetup } from 'src/core/public';
import { SearchError, getSearchErrorType } from '../../../../../../src/plugins/data/public'; import {
SearchError,
getSearchErrorType,
IIndexPattern,
SearchStrategyProvider,
SearchResponse,
SearchRequest,
} from '../../../../../../src/plugins/data/public';
function serializeFetchParams(searchRequests) { function serializeFetchParams(searchRequests: SearchRequest[]) {
return JSON.stringify( return JSON.stringify(
searchRequests.map(searchRequestWithFetchParams => { searchRequests.map(searchRequestWithFetchParams => {
const indexPattern = const indexPattern =
@ -17,7 +24,7 @@ function serializeFetchParams(searchRequests) {
} = searchRequestWithFetchParams; } = searchRequestWithFetchParams;
const query = { const query = {
size: size, size,
aggregations: aggs, aggregations: aggs,
query: _query, query: _query,
}; };
@ -30,7 +37,7 @@ function serializeFetchParams(searchRequests) {
// Rollup search always returns 0 hits, but visualizations expect search responses // Rollup search always returns 0 hits, but visualizations expect search responses
// to return hits > 0, otherwise they do not render. We fake the number of hits here // to return hits > 0, otherwise they do not render. We fake the number of hits here
// by counting the number of aggregation buckets/values returned by rollup search. // by counting the number of aggregation buckets/values returned by rollup search.
function shimHitsInFetchResponse(response) { function shimHitsInFetchResponse(response: SearchResponse[]) {
return response.map(result => { return response.map(result => {
const buckets = result.aggregations const buckets = result.aggregations
? Object.keys(result.aggregations).reduce((allBuckets, agg) => { ? Object.keys(result.aggregations).reduce((allBuckets, agg) => {
@ -51,17 +58,16 @@ function shimHitsInFetchResponse(response) {
}); });
} }
export const rollupSearchStrategy = { export const getRollupSearchStrategy = (fetch: HttpSetup['fetch']): SearchStrategyProvider => ({
id: 'rollup', id: 'rollup',
search: ({ searchRequests, Promise }) => { search: ({ searchRequests }) => {
// Serialize the fetch params into a format suitable for the body of an ES query. // Serialize the fetch params into a format suitable for the body of an ES query.
const serializedFetchParams = serializeFetchParams(searchRequests); const serializedFetchParams = serializeFetchParams(searchRequests);
const controller = new AbortController(); const controller = new AbortController();
const promise = kfetch({ const promise = fetch('../api/rollup/search', {
signal: controller.signal, signal: controller.signal,
pathname: '../api/rollup/search',
method: 'POST', method: 'POST',
body: serializedFetchParams, body: serializedFetchParams,
}); });
@ -69,17 +75,17 @@ export const rollupSearchStrategy = {
return { return {
searching: promise.then(shimHitsInFetchResponse).catch(error => { searching: promise.then(shimHitsInFetchResponse).catch(error => {
const { const {
body: { statusText, error: title, message }, body: { statusCode, error: title, message },
res: { url }, res: { url },
} = error; } = error;
// Format kfetch error as a SearchError. // Format fetch error as a SearchError.
const searchError = new SearchError({ const searchError = new SearchError({
status: statusText, status: statusCode,
title, title,
message: `Rollup search error: ${message}`, message: `Rollup search error: ${message}`,
path: url, path: url,
type: getSearchErrorType({ message }), type: getSearchErrorType({ message }) || '',
}); });
return Promise.reject(searchError); return Promise.reject(searchError);
@ -88,11 +94,11 @@ export const rollupSearchStrategy = {
}; };
}, },
isViable: indexPattern => { isViable: (indexPattern: IIndexPattern) => {
if (!indexPattern) { if (!indexPattern) {
return false; return false;
} }
return indexPattern.type === 'rollup'; return indexPattern.type === 'rollup';
}, },
}; });

View file

@ -1,14 +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.
*/
let httpClient;
export const setHttpClient = client => {
httpClient = client;
};
export async function getRollupIndices() {
const response = await httpClient.get('/api/rollup/indices');
return response || {};
}

View file

@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; export function initAggTypeFieldFilter(aggTypeFieldFilters) {
export function initAggTypeFieldFilter() {
/** /**
* If rollup index pattern, check its capabilities * If rollup index pattern, check its capabilities
* and limit available fields for a given aggType based on that. * and limit available fields for a given aggType based on that.

View file

@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { aggTypeFilters } from 'ui/agg_types/filter'; export function initAggTypeFilter(aggTypeFilters) {
export function initAggTypeFilter() {
/** /**
* If rollup index pattern, check its capabilities * If rollup index pattern, check its capabilities
* and limit available aggregations based on that. * and limit available aggregations based on that.

View file

@ -5,9 +5,8 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { editorConfigProviders } from 'ui/vis/config';
export function initEditorConfig() { export function initEditorConfig(editorConfigProviders) {
// Limit agg params based on rollup capabilities // Limit agg params based on rollup capabilities
editorConfigProviders.register((indexPattern, aggConfig) => { editorConfigProviders.register((indexPattern, aggConfig) => {
if (indexPattern.type !== 'rollup') { if (indexPattern.type !== 'rollup') {

View file

@ -1,21 +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 chrome from 'ui/chrome';
import { initAggTypeFilter } from './agg_type_filter';
import { initAggTypeFieldFilter } from './agg_type_field_filter';
import { initEditorConfig } from './editor_config';
import { CONFIG_ROLLUPS } from '../../common';
const uiSettings = chrome.getUiSettingsClient();
const isRollupIndexPatternsEnabled = uiSettings.get(CONFIG_ROLLUPS);
if (isRollupIndexPatternsEnabled) {
initAggTypeFilter();
initAggTypeFieldFilter();
initEditorConfig();
}