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.
*/
export { aggTypeFilters } from './agg_type_filters';
export { aggTypeFilters, AggTypeFilters } from './agg_type_filters';
export { propFilter } from './prop_filter';

View file

@ -17,4 +17,4 @@
* 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(),
},
},
management: {
sections: {
getSection: () => ({
registerApp: sinon.fake(),
}),
},
},
},
};
@ -167,6 +174,11 @@ export const npStart = {
hasItem: sinon.fake(),
}),
},
sections: {
getSection: () => ({
registerApp: sinon.fake(),
}),
},
},
embeddable: {
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 { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/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 { UsageCollectionSetup } from '../../../../plugins/usage_collection/public';
import {
@ -54,6 +54,7 @@ export interface PluginsSetup {
kibana_legacy: KibanaLegacySetup;
share: SharePluginSetup;
usageCollection: UsageCollectionSetup;
management: ManagementSetup;
}
export interface PluginsStart {

View file

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

View file

@ -47,6 +47,8 @@ export {
hasSearchStategyForIndexPattern,
defaultSearchStrategy,
SearchError,
SearchStrategyProvider,
getSearchErrorType,
} from './search_strategy';
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 { setupEnvironment } from './setup_environment';
export { mockHttpRequest } from './setup_environment';
export { wrapComponent } from './setup_context';
export const pageHelpers = {
jobCreate: { setup: jobCreateSetup },

View file

@ -10,8 +10,10 @@ import { JobCreate } from '../../../public/crud_app/sections';
import { JOB_TO_CLONE } from './constants';
import { deserializeJob } from '../../../public/crud_app/services';
import { wrapComponent } from './setup_context';
export const setup = props => {
const initTestBed = registerTestBed(JobCreate, {
const initTestBed = registerTestBed(wrapComponent(JobCreate), {
store: createRollupJobsStore({
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';
const initTestBed = registerTestBed(JobCreate, { store: rollupJobsStore });
import { wrapComponent } from './setup_context';
const initTestBed = registerTestBed(wrapComponent(JobCreate), { store: rollupJobsStore });
export const setup = 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 { JobList } from '../../../public/crud_app/sections/job_list';
import { wrapComponent } from './setup_context';
const testBedConfig = {
store: createRollupJobsStore,
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.
*/
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';
jest.mock('ui/new_platform');
@ -17,26 +18,28 @@ const {
} = JOB_TO_CLONE;
describe('Cloning a rollup job through create job wizard', () => {
let httpRequestsMockHelpers;
let server;
let find;
let exists;
let form;
let table;
let actions;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
httpRequestsMockHelpers.setIndexPatternValidityResponse(JOB_CLONE_INDEX_PATTERN_CHECK);
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: JOB_CLONE_INDEX_PATTERN_CHECK });
({ exists, find, form, actions, table } = setup());
});
afterAll(() => {
server.restore();
afterEach(() => {
npStart.core.http.get.mockClear();
npStart.core.http.post.mockClear();
npStart.core.http.put.mockClear();
});
it('should have fields correctly pre-populated', async () => {

View file

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

View file

@ -4,7 +4,8 @@
* 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');
@ -13,8 +14,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 4: Histogram', () => {
let server;
let httpRequestsMockHelpers;
let find;
let exists;
let actions;
@ -22,22 +21,26 @@ describe('Create Rollup Job, step 4: Histogram', () => {
let goToStep;
let table;
let form;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
});
afterAll(() => {
server.restore();
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
// Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse();
mockHttpRequest(npStart.core.http);
({ 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 goToStepAndOpenFieldChooser = async () => {
@ -108,7 +111,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
describe('when no histogram fields are availalbe', () => {
it('should indicate it to the user', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: [] });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: [] } });
await goToStepAndOpenFieldChooser();
const { tableCellsValues } = table.getMetaData('rollupJobHistogramFieldChooser-table');
@ -119,7 +122,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
describe('when histogram fields are available', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
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 () => {
// First let's add a term to the list
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = table.getMetaData('rollupJobHistogramFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click');
@ -180,7 +183,7 @@ describe('Create Rollup Job, step 4: Histogram', () => {
};
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields } });
await goToStep(4);
addHistogramFieldToList();
});

View file

@ -13,7 +13,8 @@ import {
YEAR,
} from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor';
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');
@ -22,29 +23,31 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 1: Logistics', () => {
let server;
let httpRequestsMockHelpers;
let find;
let exists;
let actions;
let form;
let getEuiStepsHorizontalActive;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
});
afterAll(() => {
server.restore();
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
// Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse();
mockHttpRequest(npStart.core.http);
({ 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"', () => {
expect(getEuiStepsHorizontalActive()).toContain('Logistics');
});
@ -94,14 +97,14 @@ describe('Create Rollup Job, step 1: Logistics', () => {
});
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);
actions.clickNextStep();
expect(form.getErrorsMessages()).toContain("Index pattern doesn't match any indices.");
});
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);
actions.clickNextStep();
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 () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ doesMatchRollupIndices: true });
mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: { doesMatchRollupIndices: true },
});
await form.setInputValue('rollupIndexPattern', 'abc', true);
actions.clickNextStep();
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.
*/
import { setupEnvironment, pageHelpers } from './helpers';
import { setHttp } from '../../public/crud_app/services';
import { mockHttpRequest, pageHelpers } from './helpers';
jest.mock('ui/new_platform');
@ -13,8 +14,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 5: Metrics', () => {
let server;
let httpRequestsMockHelpers;
let find;
let exists;
let actions;
@ -22,22 +21,26 @@ describe('Create Rollup Job, step 5: Metrics', () => {
let goToStep;
let table;
let metrics;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
});
afterAll(() => {
server.restore();
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
// Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse();
mockHttpRequest(npStart.core.http);
({ 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 dateFields = ['b-dateField', 'd-dateField'];
@ -109,7 +112,7 @@ describe('Create Rollup Job, step 5: Metrics', () => {
describe('table', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStepAndOpenFieldChooser();
});
@ -166,7 +169,7 @@ describe('Create Rollup Job, step 5: Metrics', () => {
describe('when fields are added', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStepAndOpenFieldChooser();
});
@ -257,7 +260,8 @@ describe('Create Rollup Job, step 5: Metrics', () => {
let getFieldListTableRows;
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, dateFields });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields, dateFields } });
await goToStep(5);
await addFieldToList('numeric');
await addFieldToList('date');

View file

@ -4,8 +4,9 @@
* 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 { setHttp } from '../../public/crud_app/services';
import { JOBS } from './helpers/constants';
jest.mock('ui/new_platform');
@ -15,8 +16,6 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 6: Review', () => {
let server;
let httpRequestsMockHelpers;
let find;
let exists;
let actions;
@ -24,21 +23,25 @@ describe('Create Rollup Job, step 6: Review', () => {
let goToStep;
let table;
let form;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
});
afterAll(() => {
server.restore();
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
// Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse();
mockHttpRequest(npStart.core.http);
({ 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', () => {
beforeEach(async () => {
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 () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: ['my-field'] });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: ['my-field'] } });
await goToStep(3);
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 () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields: ['a-field'] });
mockHttpRequest(npStart.core.http, { indxPatternVldtResp: { numericFields: ['a-field'] } });
await goToStep(4);
selectFirstField('Histogram');
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 () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({
numericFields: ['a-field'],
dateFields: ['b-field'],
mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields: ['a-field'],
dateFields: ['b-field'],
},
});
await goToStep(5);
selectFirstField('Metrics');
@ -125,27 +130,30 @@ describe('Create Rollup Job, step 6: Review', () => {
describe('without starting job after creation', () => {
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);
expect(server.requests.find(r => r.url === jobCreateApiPath)).toBe(undefined); // 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.put).not.toHaveBeenCalledWith(jobCreateApiPath); // make sure it hasn't been called
expect(npStart.core.http.get).not.toHaveBeenCalledWith(jobStartApiPath); // make sure it hasn't been called
actions.clickSave();
// 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 :(
await new Promise(res => setTimeout(res, 750));
expect(server.requests.find(r => r.url === jobCreateApiPath)).not.toBe(undefined); // It has been called!
expect(server.requests.find(r => r.url === jobStartApiPath)).toBe(undefined); // It has still not been called!
expect(npStart.core.http.put).toHaveBeenCalledWith(jobCreateApiPath, expect.anything()); // It has been called!
expect(npStart.core.http.get).not.toHaveBeenCalledWith(jobStartApiPath); // It has still not been called!
});
});
describe('with starting job after creation', () => {
it('should call the "create" and "start" Api server endpoints', async () => {
httpRequestsMockHelpers.setCreateJobResponse(first(JOBS.jobs));
httpRequestsMockHelpers.setStartJobResponse();
mockHttpRequest(npStart.core.http, {
createdJob: first(JOBS.jobs),
});
await goToStep(6);
@ -153,14 +161,14 @@ describe('Create Rollup Job, step 6: Review', () => {
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();
// 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 :(
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.
*/
import { setupEnvironment, pageHelpers } from './helpers';
import { setHttp } from '../../public/crud_app/services';
import { pageHelpers, mockHttpRequest } from './helpers';
jest.mock('ui/new_platform');
@ -13,30 +14,30 @@ jest.mock('lodash/function/debounce', () => fn => fn);
const { setup } = pageHelpers.jobCreate;
describe('Create Rollup Job, step 3: Terms', () => {
let server;
let httpRequestsMockHelpers;
let find;
let exists;
let actions;
let getEuiStepsHorizontalActive;
let goToStep;
let table;
let npStart;
beforeAll(() => {
({ server, httpRequestsMockHelpers } = setupEnvironment());
});
afterAll(() => {
server.restore();
npStart = require('ui/new_platform').npStart; // eslint-disable-line
setHttp(npStart.core.http);
});
beforeEach(() => {
// Set "default" mock responses by not providing any arguments
httpRequestsMockHelpers.setIndexPatternValidityResponse();
mockHttpRequest(npStart.core.http);
({ find, exists, actions, getEuiStepsHorizontalActive, goToStep, table } = setup());
});
afterEach(() => {
npStart.core.http.get.mockClear();
});
const numericFields = ['a-numericField', 'c-numericField'];
const keywordFields = ['b-keywordField', 'd-keywordField'];
@ -108,9 +109,11 @@ describe('Create Rollup Job, step 3: Terms', () => {
describe('when no terms are available', () => {
it('should indicate it to the user', async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({
numericFields: [],
keywordFields: [],
mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields: [],
keywordFields: [],
},
});
await goToStepAndOpenFieldChooser();
@ -122,7 +125,12 @@ describe('Create Rollup Job, step 3: Terms', () => {
describe('when terms are available', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, keywordFields });
mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields,
keywordFields,
},
});
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 () => {
// First let's add a term to the list
httpRequestsMockHelpers.setIndexPatternValidityResponse({ numericFields, keywordFields });
mockHttpRequest(npStart.core.http, {
indxPatternVldtResp: {
numericFields,
keywordFields,
},
});
await goToStepAndOpenFieldChooser();
const { rows: fieldChooserRows } = table.getMetaData('rollupJobTermsFieldChooser-table');
fieldChooserRows[0].reactWrapper.simulate('click');

View file

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

View file

@ -26,7 +26,7 @@ export function rollup(kibana) {
require: ['kibana', 'elasticsearch', 'xpack_main'],
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
managementSections: ['plugins/rollup/crud_app'],
managementSections: ['plugins/rollup/legacy'],
uiSettingDefaults: {
[CONFIG_ROLLUPS]: {
name: i18n.translate('xpack.rollupJobs.rollupIndexPatternsTitle', {
@ -41,13 +41,9 @@ export function rollup(kibana) {
category: ['rollups'],
},
},
indexManagement: [
'plugins/rollup/index_pattern_creation',
'plugins/rollup/index_pattern_list',
'plugins/rollup/extend_index_management',
],
visualize: ['plugins/rollup/visualize'],
search: ['plugins/rollup/search'],
indexManagement: ['plugins/rollup/legacy'],
visualize: ['plugins/rollup/legacy'],
search: ['plugins/rollup/legacy'],
},
init: function(server) {
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 */
}
.rollupJobsRoot {
display: flex;
}
/**
* 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 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';
class ConfirmDeleteModalUi extends Component {
export class ConfirmDeleteModal extends Component {
static propTypes = {
isSingleSelection: PropTypes.bool.isRequired,
jobs: PropTypes.array.isRequired,
@ -19,12 +20,14 @@ class ConfirmDeleteModalUi extends Component {
};
renderJobs() {
const { jobs, intl } = this.props;
const { jobs } = this.props;
const jobItems = jobs.map(({ id, status }) => {
const startedMessage = intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.startedMessage',
defaultMessage: 'started',
});
const startedMessage = i18n.translate(
'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.startedMessage',
{
defaultMessage: 'started',
}
);
const statusText = status === 'started' ? ` (${startedMessage})` : null;
return (
<li key={id}>
@ -38,19 +41,19 @@ class ConfirmDeleteModalUi extends Component {
}
render() {
const { isSingleSelection, jobs, onCancel, onConfirm, intl } = this.props;
const { isSingleSelection, jobs, onCancel, onConfirm } = this.props;
let title;
let content;
if (isSingleSelection) {
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}'?",
},
{ id }
values: { id },
}
);
if (status === 'started') {
@ -64,12 +67,12 @@ class ConfirmDeleteModalUi extends Component {
);
}
} 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?',
},
{ count: jobs.length }
values: { count: jobs.length },
}
);
content = (
@ -92,15 +95,19 @@ class ConfirmDeleteModalUi extends Component {
title={title}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.cancelButtonText',
defaultMessage: 'Cancel',
})}
cancelButtonText={i18n.translate(
'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel',
}
)}
buttonColor="danger"
confirmButtonText={intl.formatMessage({
id: 'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.confirmButtonText',
defaultMessage: 'Delete',
})}
confirmButtonText={i18n.translate(
'xpack.rollupJobs.jobActionMenu.deleteJob.confirmModal.confirmButtonText',
{
defaultMessage: 'Delete',
}
)}
>
{content}
</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 { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
import { FormattedMessage } from '@kbn/i18n/react';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/';
import {
EuiCallOut,
@ -27,8 +27,6 @@ import {
EuiTitle,
} from '@elastic/eui';
import { fatalError } from 'ui/notify';
import {
validateIndexPattern,
formatFields,
@ -59,6 +57,8 @@ import {
hasErrors,
} from './steps_config';
import { getFatalErrors } from '../../../kibana_services';
const stepIdToTitleMap = {
[STEP_LOGISTICS]: i18n.translate('xpack.rollupJobs.create.steps.stepLogisticsTitle', {
defaultMessage: 'Logistics',
@ -92,7 +92,7 @@ export class JobCreateUi extends Component {
constructor(props) {
super(props);
chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb, createBreadcrumb]);
props.kibana.services.setBreadcrumbs([listBreadcrumb, createBreadcrumb]);
const { jobToClone: stepDefaultOverrides } = props;
const stepsFields = mapValues(stepIdToStepConfigMap, step =>
cloneDeep(step.getDefaultFields(stepDefaultOverrides))
@ -181,7 +181,7 @@ export class JobCreateUi extends Component {
dateFields: indexPatternDateFields,
numericFields,
keywordFields,
} = response.data;
} = response;
let indexPatternAsyncErrors;
@ -298,9 +298,9 @@ export class JobCreateUi extends Component {
return;
}
// Expect an error in the shape provided by Angular's $http service.
if (error && error.data) {
const { error: errorString, statusCode } = error.data;
// Expect an error in the shape provided by http service.
if (error && error.body) {
const { error: errorString, statusCode } = error.body;
const indexPatternAsyncErrors = [
<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
// unexpected happened.
fatalError(
getFatalErrors().add(
error,
i18n.translate('xpack.rollupJobs.create.errors.indexPatternValidationFatalErrorTitle', {
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 PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
@ -18,7 +18,7 @@ import {
EuiCheckbox,
} from '@elastic/eui';
const NavigationUi = ({
export const Navigation = ({
isSaving,
hasNextStep,
hasPreviousStep,
@ -120,7 +120,7 @@ const NavigationUi = ({
);
};
NavigationUi.propTypes = {
Navigation.propTypes = {
hasNextStep: PropTypes.bool.isRequired,
hasPreviousStep: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
@ -129,5 +129,3 @@ NavigationUi.propTypes = {
save: PropTypes.func.isRequired,
canGoToNextStep: PropTypes.bool.isRequired,
};
export const Navigation = injectI18n(NavigationUi);

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import get from 'lodash/object/get';
@ -22,7 +22,7 @@ import {
EuiButton,
} from '@elastic/eui';
import { metricsDetailsUrl } from '../../../services';
import { getMetricsDetailsUrl } from '../../../services';
import { FieldList } from '../../components';
import { FieldChooser, StepError } from './components';
import { METRICS_CONFIG } from '../../../constants';
@ -64,7 +64,7 @@ const metricTypesConfig = (function() {
});
})();
export class StepMetricsUi extends Component {
export class StepMetrics extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired,
@ -247,7 +247,7 @@ export class StepMetricsUi extends Component {
}
getListColumns() {
return StepMetricsUi.chooserColumns.concat({
return StepMetrics.chooserColumns.concat({
type: 'metrics',
name: i18n.translate('xpack.rollupJobs.create.stepMetrics.metricsColumnHeader', {
defaultMessage: 'Metrics',
@ -384,7 +384,7 @@ export class StepMetricsUi extends Component {
<EuiButtonEmpty
size="s"
flush="right"
href={metricsDetailsUrl}
href={getMetricsDetailsUrl()}
target="_blank"
iconType="help"
data-test-subj="rollupJobCreateMetricsDocsButton"
@ -421,7 +421,7 @@ export class StepMetricsUi extends Component {
defaultMessage="Add metrics fields"
/>
}
columns={StepMetricsUi.chooserColumns}
columns={StepMetrics.chooserColumns}
fields={metricsFields}
selectedFields={metrics}
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 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';
@ -30,7 +30,7 @@ const JOB_DETAILS_TABS = [
JOB_DETAILS_TAB_REQUEST,
];
export class StepReviewUi extends Component {
export class StepReview extends Component {
static propTypes = {
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 PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButtonEmpty,
@ -17,13 +17,13 @@ import {
EuiTitle,
} from '@elastic/eui';
import { termsDetailsUrl } from '../../../services';
import { getTermsDetailsUrl } from '../../../services';
import { FieldList } from '../../components';
import { FieldChooser } from './components';
export class StepTermsUi extends Component {
export class StepTerms extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
onFieldsChange: PropTypes.func.isRequired,
@ -99,7 +99,7 @@ export class StepTermsUi extends Component {
<EuiButtonEmpty
size="s"
flush="right"
href={termsDetailsUrl}
href={getTermsDetailsUrl()}
target="_blank"
iconType="help"
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 { FormattedMessage } from '@kbn/i18n/react';
import { findIllegalCharactersInIndexName } from 'ui/indices';
import { findIllegalCharactersInIndexName } from '../../../../legacy_imports';
export function validateRollupIndex(rollupIndex, indexPattern) {
if (!rollupIndex || !rollupIndex.trim()) {

View file

@ -6,7 +6,8 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiErrorBoundary,
@ -62,7 +63,7 @@ const tabToUiMetricMap = {
[JOB_DETAILS_TAB_JSON]: UIM_DETAIL_PANEL_JSON_TAB_CLICK,
};
export class DetailPanelUi extends Component {
export class DetailPanel extends Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
isLoading: PropTypes.bool,
@ -130,7 +131,7 @@ export class DetailPanelUi extends Component {
}
renderJob() {
const { panelType, job, intl } = this.props;
const { panelType, job } = this.props;
const { status, documentsProcessed, pagesProcessed, rollupsIndexed, triggerCount, json } = job;
@ -159,8 +160,7 @@ export class DetailPanelUi extends Component {
anchorPosition="upRight"
detailPanel={true}
iconType="arrowUp"
label={intl.formatMessage({
id: 'xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel',
label={i18n.translate('xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel', {
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", () => {
expect(component.find('DetailPanelUi').children().length).toBeTruthy();
expect(component.find('DetailPanel').children().length).toBeTruthy();
});
it('should *not* have children if its closed', () => {
({ 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', () => {

View file

@ -6,9 +6,8 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import {
EuiButton,
@ -26,6 +25,8 @@ import {
EuiCallOut,
} from '@elastic/eui';
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/';
import { CRUD_APP_BASE_PATH } from '../../constants';
import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services';
@ -67,7 +68,7 @@ export class JobListUi extends Component {
props.loadJobs();
chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb]);
props.kibana.services.setBreadcrumbs([listBreadcrumb]);
this.state = {};
}
@ -97,9 +98,7 @@ export class JobListUi extends Component {
}
renderNoPermission() {
const { intl } = this.props;
const title = intl.formatMessage({
id: 'xpack.rollupJobs.jobList.noPermissionTitle',
const title = i18n.translate('xpack.rollupJobs.jobList.noPermissionTitle', {
defaultMessage: 'Permission error',
});
return (
@ -122,13 +121,11 @@ export class JobListUi extends Component {
}
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.
const { statusCode, error: errorString } = error.data;
const { statusCode, error: errorString } = error.body;
const { intl } = this.props;
const title = intl.formatMessage({
id: 'xpack.rollupJobs.jobList.loadingErrorTitle',
const title = i18n.translate('xpack.rollupJobs.jobList.loadingErrorTitle', {
defaultMessage: 'Error loading rollup jobs',
});
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.
*/
import React from 'react';
import { registerTestBed } from '../../../../../../../test_utils';
import { rollupJobsStore } from '../../store';
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/chrome', () => ({
addBasePath: () => {},
breadcrumbs: { set: () => {} },
getInjected: key => {
if (key === 'uiCapabilities') {
return {
navLinks: {},
management: {},
catalogue: {},
};
}
},
}));
jest.mock('../../services', () => {
const services = require.requireActual('../../services');
@ -40,7 +32,16 @@ const defaultProps = {
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 />', () => {
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 });
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', () => {
const { component, exists } = initTestBed({ hasJobs: true });
expect(exists('jobListLoading')).toBeFalsy();
expect(component.find('JobTableUi').length).toBeTruthy();
expect(component.find('JobTable').length).toBeTruthy();
});
describe('when there is an API error', () => {
const { exists, find } = initTestBed({
jobLoadError: {
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 PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiCheckbox,
@ -120,7 +120,7 @@ const COLUMNS = [
},
];
export class JobTableUi extends Component {
export class JobTable extends Component {
static propTypes = {
jobs: PropTypes.array,
pager: PropTypes.object.isRequired,
@ -333,7 +333,7 @@ export class JobTableUi extends Component {
}
render() {
const { filterChanged, filter, jobs, intl, closeDetailPanel } = this.props;
const { filterChanged, filter, jobs, closeDetailPanel } = this.props;
const { idToSelectedJobMap } = this.state;
@ -360,8 +360,7 @@ export class JobTableUi extends Component {
filterChanged(event.target.value);
}}
data-test-subj="jobTableFilterInput"
placeholder={intl.formatMessage({
id: 'xpack.rollupJobs.jobTable.searchInputPlaceholder',
placeholder={i18n.translate('xpack.rollupJobs.jobTable.searchInputPlaceholder', {
defaultMessage: 'Search',
})}
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.
*/
import chrome from 'ui/chrome';
import {
UIM_JOB_CREATE,
UIM_JOB_DELETE,
@ -17,39 +16,45 @@ import {
import { getHttp } from './http_provider';
import { trackUserRequest } from './track_ui_metric';
const apiPrefix = chrome.addBasePath('/api/rollup');
const apiPrefix = '/api/rollup';
export async function loadJobs() {
const {
data: { jobs },
} = await getHttp().get(`${apiPrefix}/jobs`);
const { jobs } = await getHttp().get(`${apiPrefix}/jobs`);
return jobs;
}
export async function startJobs(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;
return await trackUserRequest(request, actionType);
}
export async function stopJobs(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;
return await trackUserRequest(request, actionType);
}
export async function deleteJobs(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;
return await trackUserRequest(request, actionType);
}
export async function createJob(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);
}

View file

@ -4,12 +4,12 @@
* 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) {
// Expect an error in the shape provided by Angular's $http service.
if (error && error.data) {
const { error: errorString, statusCode, message } = error.data;
function createToastConfig(error: any, errorTitle: string) {
// Expect an error in the shape provided by http service.
if (error && error.body) {
const { error: errorString, statusCode, message } = error.body;
return {
title: errorTitle,
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);
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
// 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);
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
// 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.
*/
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}`;
const xPackBase = `${ELASTIC_WEBSITE_URL}guide/en/x-pack/${DOC_LINK_VERSION}`;
export function setEsBaseAndXPackBase(elasticWebsiteUrl, docLinksVersion) {
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 dateHistogramDetailsUrl = `${esBase}/rollup-job-config.html#_date_histogram_2`;
export const termsDetailsUrl = `${esBase}/rollup-job-config.html#_terms_2`;
export const histogramDetailsUrl = `${esBase}/rollup-job-config.html#_histogram_2`;
export const metricsDetailsUrl = `${esBase}/rollup-job-config.html#rollup-metrics-config`;
export const getLogisticalDetailsUrl = () => `${esBase}/rollup-job-config.html#_logistical_details`;
export const getDateHistogramDetailsUrl = () =>
`${esBase}/rollup-job-config.html#_date_histogram_2`;
export const getTermsDetailsUrl = () => `${esBase}/rollup-job-config.html#_terms_2`;
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 cronUrl = `${xPackBase}/trigger-schedule.html#_cron_expressions`;
export const getDateHistogramAggregationUrl = () =>
`${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.
*/
// This is an Angular service, which is why we use this provider pattern to access it within
// our React app.
let _http;
import { HttpStart } from 'src/core/public';
export function setHttp(http) {
let _http: HttpStart | null = null;
export function setHttp(http: HttpStart) {
_http = http;
}
export function getHttp() {
if (!_http) {
throw new Error('Rollup http is not defined');
}
return _http;
}

View file

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

View file

@ -5,7 +5,6 @@
*/
import { i18n } from '@kbn/i18n';
import { fatalError } from 'ui/notify';
import { CRUD_APP_BASE_PATH } from '../../constants';
import {
@ -24,6 +23,8 @@ import {
CLEAR_CREATE_JOB_ERRORS,
} from '../action_types';
import { getFatalErrors } from '../../../kibana_services';
export const createJob = jobConfig => async dispatch => {
dispatch({
type: CREATE_JOB_START,
@ -39,12 +40,13 @@ export const createJob = jobConfig => async dispatch => {
]);
} catch (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.
if (data) {
// Expect an error in the shape provided by http service.
if (body) {
// Some errors have statusCode directly available but some are under a data property.
if ((statusCode || (data && data.statusCode)) === 409) {
if (statusCode === 409) {
return dispatch({
type: CREATE_JOB_FAILURE,
payload: {
@ -67,9 +69,9 @@ export const createJob = jobConfig => async dispatch => {
error: {
message: i18n.translate('xpack.rollupJobs.createAction.failedDefaultErrorMessage', {
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
// unexpected happened.
return fatalError(
return getFatalErrors().add(
error,
i18n.translate('xpack.rollupJobs.createAction.errorTitle', {
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({
type: CREATE_JOB_SUCCESS,

View file

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

View file

@ -4,15 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import {
addToggleExtension,
addBadgeExtension,
} from '../../../index_management/public/index_management_extensions';
import { get } from 'lodash';
const propertyPath = 'isRollupIndex';
export const rollupToggleExtension = {
matchIndex: index => {
matchIndex: (index: { isRollupIndex: boolean }) => {
return get(index, propertyPath);
},
label: i18n.translate('xpack.rollupJobs.indexMgmtToggle.toggleLabel', {
@ -20,8 +16,9 @@ export const rollupToggleExtension = {
}),
name: 'rollupToggle',
};
export const rollupBadgeExtension = {
matchIndex: index => {
matchIndex: (index: { isRollupIndex: boolean }) => {
return get(index, propertyPath);
},
label: i18n.translate('xpack.rollupJobs.indexMgmtBadge.rollupLabel', {
@ -30,6 +27,3 @@ export const rollupBadgeExtension = {
color: 'secondary',
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 { i18n } from '@kbn/i18n';
import { npSetup } from 'ui/new_platform';
import { RollupPrompt } from './components/rollup_prompt';
import { setHttpClient, getRollupIndices } from '../services/api';
import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public';
const rollupIndexPatternTypeName = i18n.translate(
@ -54,7 +52,6 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
...options,
});
setHttpClient(this.httpClient);
this.rollupIndex = null;
this.rollupJobs = [];
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
// 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.
const isAnonymous = npSetup.core.http.anonymousPaths.isAnonymous(window.location.pathname);
const isAnonymous = this.httpClient.anonymousPaths.isAnonymous(window.location.pathname);
if (!isAnonymous) {
this.rollupIndicesCapabilities = await getRollupIndices();
const response = await this.httpClient.get('/api/rollup/indices');
this.rollupIndicesCapabilities = response || {};
}
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.
*/
import { kfetch } from 'ui/kfetch';
import { SearchError, getSearchErrorType } from '../../../../../../src/plugins/data/public';
import { HttpSetup } from 'src/core/public';
import {
SearchError,
getSearchErrorType,
IIndexPattern,
SearchStrategyProvider,
SearchResponse,
SearchRequest,
} from '../../../../../../src/plugins/data/public';
function serializeFetchParams(searchRequests) {
function serializeFetchParams(searchRequests: SearchRequest[]) {
return JSON.stringify(
searchRequests.map(searchRequestWithFetchParams => {
const indexPattern =
@ -17,7 +24,7 @@ function serializeFetchParams(searchRequests) {
} = searchRequestWithFetchParams;
const query = {
size: size,
size,
aggregations: aggs,
query: _query,
};
@ -30,7 +37,7 @@ function serializeFetchParams(searchRequests) {
// 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
// by counting the number of aggregation buckets/values returned by rollup search.
function shimHitsInFetchResponse(response) {
function shimHitsInFetchResponse(response: SearchResponse[]) {
return response.map(result => {
const buckets = result.aggregations
? 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',
search: ({ searchRequests, Promise }) => {
search: ({ searchRequests }) => {
// Serialize the fetch params into a format suitable for the body of an ES query.
const serializedFetchParams = serializeFetchParams(searchRequests);
const controller = new AbortController();
const promise = kfetch({
const promise = fetch('../api/rollup/search', {
signal: controller.signal,
pathname: '../api/rollup/search',
method: 'POST',
body: serializedFetchParams,
});
@ -69,17 +75,17 @@ export const rollupSearchStrategy = {
return {
searching: promise.then(shimHitsInFetchResponse).catch(error => {
const {
body: { statusText, error: title, message },
body: { statusCode, error: title, message },
res: { url },
} = error;
// Format kfetch error as a SearchError.
// Format fetch error as a SearchError.
const searchError = new SearchError({
status: statusText,
status: statusCode,
title,
message: `Rollup search error: ${message}`,
path: url,
type: getSearchErrorType({ message }),
type: getSearchErrorType({ message }) || '',
});
return Promise.reject(searchError);
@ -88,11 +94,11 @@ export const rollupSearchStrategy = {
};
},
isViable: indexPattern => {
isViable: (indexPattern: IIndexPattern) => {
if (!indexPattern) {
return false;
}
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.
*/
import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter';
export function initAggTypeFieldFilter() {
export function initAggTypeFieldFilter(aggTypeFieldFilters) {
/**
* If rollup index pattern, check its capabilities
* 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.
*/
import { aggTypeFilters } from 'ui/agg_types/filter';
export function initAggTypeFilter() {
export function initAggTypeFilter(aggTypeFilters) {
/**
* If rollup index pattern, check its capabilities
* and limit available aggregations based on that.

View file

@ -5,9 +5,8 @@
*/
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
editorConfigProviders.register((indexPattern, aggConfig) => {
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();
}