mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Merge remote-tracking branch 'origin/master' into feature/merge-code
This commit is contained in:
commit
3a1a49f354
192 changed files with 2923 additions and 2556 deletions
|
@ -68,8 +68,8 @@ security is enabled, `xpack.security.encryptionKey`.
|
|||
============
|
||||
|
||||
`xpack.reporting.queue.pollInterval`::
|
||||
Specifies the number of milliseconds that idle workers wait between polling the
|
||||
index for pending jobs. Defaults to `3000` (3 seconds).
|
||||
Specifies the number of milliseconds that the reporting poller waits between polling the
|
||||
index for any pending Reporting jobs. Defaults to `3000` (3 seconds).
|
||||
|
||||
[[xpack-reporting-q-timeout]]`xpack.reporting.queue.timeout`::
|
||||
How long each worker has to produce a report. If your machine is slow or under
|
||||
|
|
|
@ -122,7 +122,8 @@ which is proxied through the Kibana server.
|
|||
|
||||
`kibana.index:`:: *Default: ".kibana"* Kibana uses an index in Elasticsearch to
|
||||
store saved searches, visualizations and dashboards. Kibana creates a new index
|
||||
if the index doesn’t already exist.
|
||||
if the index doesn’t already exist. If you configure a custom index, the name must
|
||||
be lowercase, and conform to {es} {ref}/indices-create-index.html[index name limitations].
|
||||
|
||||
`logging.dest:`:: *Default: `stdout`* Enables you specify a file where Kibana
|
||||
stores log output.
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"test:ui:runner": "node scripts/functional_test_runner",
|
||||
"test:server": "grunt test:server",
|
||||
"test:coverage": "grunt test:coverage",
|
||||
"typespec": "typings-tester --config x-pack/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts",
|
||||
"checkLicenses": "grunt licenses --dev",
|
||||
"build": "node scripts/build --all-platforms",
|
||||
"start": "node --trace-warnings --trace-deprecation scripts/kibana --dev ",
|
||||
|
@ -411,6 +412,7 @@
|
|||
"tslint-microsoft-contrib": "^6.0.0",
|
||||
"tslint-plugin-prettier": "^2.0.0",
|
||||
"typescript": "^3.3.3333",
|
||||
"typings-tester": "^0.3.2",
|
||||
"vinyl-fs": "^3.0.2",
|
||||
"xml2js": "^0.4.19",
|
||||
"xmlbuilder": "9.0.4",
|
||||
|
|
45
src/core/public/base_path/base_path_service.mock.ts
Normal file
45
src/core/public/base_path/base_path_service.mock.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { BasePathService, BasePathStart } from './base_path_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<BasePathStart> = {
|
||||
get: jest.fn(),
|
||||
addToPath: jest.fn(),
|
||||
removeFromPath: jest.fn(),
|
||||
};
|
||||
startContract.get.mockReturnValue('get');
|
||||
startContract.addToPath.mockReturnValue('addToPath');
|
||||
startContract.removeFromPath.mockReturnValue('removeFromPath');
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type BasePathServiceContract = PublicMethodsOf<BasePathService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<BasePathServiceContract> = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const basePathServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { BasePathService } from './base_path_service';
|
||||
|
||||
function setup(options: any = {}) {
|
||||
|
@ -25,9 +26,8 @@ function setup(options: any = {}) {
|
|||
|
||||
const service = new BasePathService();
|
||||
|
||||
const injectedMetadata = {
|
||||
getBasePath: jest.fn().mockReturnValue(injectedBasePath),
|
||||
} as any;
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
injectedMetadata.getBasePath.mockReturnValue(injectedBasePath);
|
||||
|
||||
const start = service.start({
|
||||
injectedMetadata,
|
||||
|
|
60
src/core/public/chrome/chrome_service.mock.ts
Normal file
60
src/core/public/chrome/chrome_service.mock.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Brand, Breadcrumb, ChromeService, ChromeStart } from './chrome_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<ChromeStart> = {
|
||||
setBrand: jest.fn(),
|
||||
getBrand$: jest.fn(),
|
||||
setIsVisible: jest.fn(),
|
||||
getIsVisible$: jest.fn(),
|
||||
setIsCollapsed: jest.fn(),
|
||||
getIsCollapsed$: jest.fn(),
|
||||
addApplicationClass: jest.fn(),
|
||||
removeApplicationClass: jest.fn(),
|
||||
getApplicationClasses$: jest.fn(),
|
||||
getBreadcrumbs$: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
getHelpExtension$: jest.fn(),
|
||||
setHelpExtension: jest.fn(),
|
||||
};
|
||||
startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as Brand));
|
||||
startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
|
||||
startContract.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
|
||||
startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
|
||||
startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as Breadcrumb]));
|
||||
startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type ChromeServiceContract = PublicMethodsOf<ChromeService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<ChromeServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const chromeServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -20,6 +20,9 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import { toArray } from 'rxjs/operators';
|
||||
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
|
||||
const store = new Map();
|
||||
(window as any).localStorage = {
|
||||
setItem: (key: string, value: string) => store.set(String(key), String(value)),
|
||||
|
@ -31,22 +34,8 @@ import { ChromeService } from './chrome_service';
|
|||
|
||||
function defaultStartDeps(): any {
|
||||
return {
|
||||
notifications: {
|
||||
toasts: {
|
||||
addWarning: jest.fn(),
|
||||
},
|
||||
},
|
||||
injectedMetadata: {
|
||||
injectedMetadataStart: true,
|
||||
getCspConfig: jest.fn().mockReturnValue({ warnLegacyBrowsers: true }),
|
||||
getKibanaVersion: jest.fn().mockReturnValue('kibanaVersion'),
|
||||
getLegacyMetadata: jest.fn().mockReturnValue({
|
||||
uiSettings: {
|
||||
defaults: { legacyInjectedUiSettingDefaults: true },
|
||||
user: { legacyInjectedUiSettingUserValues: true },
|
||||
},
|
||||
}),
|
||||
},
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,118 +17,72 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BasePathService } from './base_path';
|
||||
import { ChromeService } from './chrome';
|
||||
import { FatalErrorsService } from './fatal_errors';
|
||||
import { HttpService } from './http';
|
||||
import { I18nService } from './i18n';
|
||||
import { InjectedMetadataService } from './injected_metadata';
|
||||
import { LegacyPlatformService } from './legacy';
|
||||
import { NotificationsService } from './notifications';
|
||||
import { UiSettingsService } from './ui_settings';
|
||||
|
||||
const MockLegacyPlatformService = jest.fn<LegacyPlatformService, any>(
|
||||
function _MockLegacyPlatformService(this: any) {
|
||||
this.start = jest.fn();
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
}
|
||||
);
|
||||
import { basePathServiceMock } from './base_path/base_path_service.mock';
|
||||
import { chromeServiceMock } from './chrome/chrome_service.mock';
|
||||
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
|
||||
import { httpServiceMock } from './http/http_service.mock';
|
||||
import { i18nServiceMock } from './i18n/i18n_service.mock';
|
||||
import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock';
|
||||
import { legacyPlatformServiceMock } from './legacy/legacy_service.mock';
|
||||
import { notificationServiceMock } from './notifications/notifications_service.mock';
|
||||
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
|
||||
const MockLegacyPlatformService = legacyPlatformServiceMock.create();
|
||||
const LegacyPlatformServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockLegacyPlatformService);
|
||||
jest.mock('./legacy', () => ({
|
||||
LegacyPlatformService: MockLegacyPlatformService,
|
||||
LegacyPlatformService: LegacyPlatformServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockInjectedMetadataStart = {};
|
||||
const MockInjectedMetadataService = jest.fn<InjectedMetadataService, any>(
|
||||
function _MockInjectedMetadataService(this: any) {
|
||||
this.start = jest.fn().mockReturnValue(mockInjectedMetadataStart);
|
||||
return this;
|
||||
}
|
||||
);
|
||||
const MockInjectedMetadataService = injectedMetadataServiceMock.create();
|
||||
const InjectedMetadataServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockInjectedMetadataService);
|
||||
jest.mock('./injected_metadata', () => ({
|
||||
InjectedMetadataService: MockInjectedMetadataService,
|
||||
InjectedMetadataService: InjectedMetadataServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockFatalErrorsStart = {};
|
||||
const MockFatalErrorsService = jest.fn<FatalErrorsService, any>(function _MockFatalErrorsService(
|
||||
this: any
|
||||
) {
|
||||
this.start = jest.fn().mockReturnValue(mockFatalErrorsStart);
|
||||
this.add = jest.fn();
|
||||
return this;
|
||||
});
|
||||
const MockFatalErrorsService = fatalErrorsServiceMock.create();
|
||||
const FatalErrorsServiceConstructor = jest.fn().mockImplementation(() => MockFatalErrorsService);
|
||||
jest.mock('./fatal_errors', () => ({
|
||||
FatalErrorsService: MockFatalErrorsService,
|
||||
FatalErrorsService: FatalErrorsServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockI18nStart = {};
|
||||
const MockI18nService = jest.fn<I18nService, any>(function _MockI18nService(this: any) {
|
||||
this.start = jest.fn().mockReturnValue(mockI18nStart);
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
});
|
||||
const MockI18nService = i18nServiceMock.create();
|
||||
const I18nServiceConstructor = jest.fn().mockImplementation(() => MockI18nService);
|
||||
jest.mock('./i18n', () => ({
|
||||
I18nService: MockI18nService,
|
||||
I18nService: I18nServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockNotificationStart = {};
|
||||
const MockNotificationsService = jest.fn<NotificationsService, any>(
|
||||
function _MockNotificationsService(this: any) {
|
||||
this.start = jest.fn().mockReturnValue(mockNotificationStart);
|
||||
this.add = jest.fn();
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
}
|
||||
);
|
||||
|
||||
const MockNotificationsService = notificationServiceMock.create();
|
||||
const NotificationServiceConstructor = jest.fn().mockImplementation(() => MockNotificationsService);
|
||||
jest.mock('./notifications', () => ({
|
||||
NotificationsService: MockNotificationsService,
|
||||
NotificationsService: NotificationServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockHttp = {};
|
||||
const MockHttpService = jest.fn<HttpService, any>(function _MockNotificationsService(this: any) {
|
||||
this.start = jest.fn().mockReturnValue(mockHttp);
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
});
|
||||
const MockHttpService = httpServiceMock.create();
|
||||
const HttpServiceConstructor = jest.fn().mockImplementation(() => MockHttpService);
|
||||
jest.mock('./http', () => ({
|
||||
HttpService: MockHttpService,
|
||||
HttpService: HttpServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockBasePathStart = {};
|
||||
const MockBasePathService = jest.fn<BasePathService, any>(function _MockNotificationsService(
|
||||
this: any
|
||||
) {
|
||||
this.start = jest.fn().mockReturnValue(mockBasePathStart);
|
||||
return this;
|
||||
});
|
||||
const MockBasePathService = basePathServiceMock.create();
|
||||
const BasePathServiceConstructor = jest.fn().mockImplementation(() => MockBasePathService);
|
||||
jest.mock('./base_path', () => ({
|
||||
BasePathService: MockBasePathService,
|
||||
BasePathService: BasePathServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockUiSettings = {};
|
||||
const MockUiSettingsService = jest.fn<UiSettingsService, any>(function _MockNotificationsService(
|
||||
this: any
|
||||
) {
|
||||
this.start = jest.fn().mockReturnValue(mockUiSettings);
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
});
|
||||
const MockUiSettingsService = uiSettingsServiceMock.create();
|
||||
const UiSettingsServiceConstructor = jest.fn().mockImplementation(() => MockUiSettingsService);
|
||||
jest.mock('./ui_settings', () => ({
|
||||
UiSettingsService: MockUiSettingsService,
|
||||
UiSettingsService: UiSettingsServiceConstructor,
|
||||
}));
|
||||
|
||||
const mockChromeStart = {};
|
||||
const MockChromeService = jest.fn<ChromeService, any>(function _MockNotificationsService(
|
||||
this: any
|
||||
) {
|
||||
this.start = jest.fn().mockReturnValue(mockChromeStart);
|
||||
this.stop = jest.fn();
|
||||
return this;
|
||||
});
|
||||
const MockChromeService = chromeServiceMock.create();
|
||||
const ChromeServiceConstructor = jest.fn().mockImplementation(() => MockChromeService);
|
||||
jest.mock('./chrome', () => ({
|
||||
ChromeService: MockChromeService,
|
||||
ChromeService: ChromeServiceConstructor,
|
||||
}));
|
||||
|
||||
import { CoreSystem } from './core_system';
|
||||
|
@ -149,52 +103,52 @@ beforeEach(() => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function createCoreSystem(params = {}) {
|
||||
return new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
describe('constructor', () => {
|
||||
it('creates instances of services', () => {
|
||||
// tslint:disable no-unused-expression
|
||||
new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
createCoreSystem();
|
||||
|
||||
expect(MockInjectedMetadataService).toHaveBeenCalledTimes(1);
|
||||
expect(MockLegacyPlatformService).toHaveBeenCalledTimes(1);
|
||||
expect(MockI18nService).toHaveBeenCalledTimes(1);
|
||||
expect(MockFatalErrorsService).toHaveBeenCalledTimes(1);
|
||||
expect(MockNotificationsService).toHaveBeenCalledTimes(1);
|
||||
expect(MockHttpService).toHaveBeenCalledTimes(1);
|
||||
expect(MockBasePathService).toHaveBeenCalledTimes(1);
|
||||
expect(MockUiSettingsService).toHaveBeenCalledTimes(1);
|
||||
expect(MockChromeService).toHaveBeenCalledTimes(1);
|
||||
expect(InjectedMetadataServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(LegacyPlatformServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(I18nServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(NotificationServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(HttpServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(BasePathServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(UiSettingsServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('passes injectedMetadata param to InjectedMetadataService', () => {
|
||||
const injectedMetadata = { injectedMetadata: true } as any;
|
||||
|
||||
// tslint:disable no-unused-expression
|
||||
new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
createCoreSystem({
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
expect(MockInjectedMetadataService).toHaveBeenCalledTimes(1);
|
||||
expect(MockInjectedMetadataService).toHaveBeenCalledWith({
|
||||
expect(InjectedMetadataServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(InjectedMetadataServiceConstructor).toHaveBeenCalledWith({
|
||||
injectedMetadata,
|
||||
});
|
||||
});
|
||||
|
||||
it('passes requireLegacyFiles, useLegacyTestHarness, and a dom element to LegacyPlatformService', () => {
|
||||
const requireLegacyFiles = { requireLegacyFiles: true } as any;
|
||||
const useLegacyTestHarness = { useLegacyTestHarness: true } as any;
|
||||
const requireLegacyFiles = { requireLegacyFiles: true };
|
||||
const useLegacyTestHarness = { useLegacyTestHarness: true };
|
||||
|
||||
// tslint:disable no-unused-expression
|
||||
new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
createCoreSystem({
|
||||
requireLegacyFiles,
|
||||
useLegacyTestHarness,
|
||||
});
|
||||
|
||||
expect(MockLegacyPlatformService).toHaveBeenCalledTimes(1);
|
||||
expect(MockLegacyPlatformService).toHaveBeenCalledWith({
|
||||
expect(LegacyPlatformServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(LegacyPlatformServiceConstructor).toHaveBeenCalledWith({
|
||||
targetDomElement: expect.any(HTMLElement),
|
||||
requireLegacyFiles,
|
||||
useLegacyTestHarness,
|
||||
|
@ -202,46 +156,39 @@ describe('constructor', () => {
|
|||
});
|
||||
|
||||
it('passes a dom element to NotificationsService', () => {
|
||||
// tslint:disable no-unused-expression
|
||||
new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
createCoreSystem();
|
||||
|
||||
expect(MockNotificationsService).toHaveBeenCalledTimes(1);
|
||||
expect(MockNotificationsService).toHaveBeenCalledWith({
|
||||
expect(NotificationServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(NotificationServiceConstructor).toHaveBeenCalledWith({
|
||||
targetDomElement: expect.any(HTMLElement),
|
||||
});
|
||||
});
|
||||
|
||||
it('passes browserSupportsCsp to ChromeService', () => {
|
||||
new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
createCoreSystem();
|
||||
|
||||
expect(MockChromeService).toHaveBeenCalledTimes(1);
|
||||
expect(MockChromeService).toHaveBeenCalledWith({
|
||||
expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
expect(ChromeServiceConstructor).toHaveBeenCalledWith({
|
||||
browserSupportsCsp: expect.any(Boolean),
|
||||
});
|
||||
});
|
||||
|
||||
it('passes injectedMetadata, rootDomElement, and a stopCoreSystem function to FatalErrorsService', () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const injectedMetadata = { injectedMetadata: true } as any;
|
||||
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
const coreSystem = createCoreSystem({
|
||||
rootDomElement,
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
expect(MockFatalErrorsService).toHaveBeenCalledTimes(1);
|
||||
expect(MockFatalErrorsService).toHaveBeenLastCalledWith({
|
||||
expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(FatalErrorsServiceConstructor).toHaveBeenLastCalledWith({
|
||||
rootDomElement,
|
||||
injectedMetadata: expect.any(MockInjectedMetadataService),
|
||||
injectedMetadata: MockInjectedMetadataService,
|
||||
stopCoreSystem: expect.any(Function),
|
||||
});
|
||||
|
||||
const [{ stopCoreSystem }] = MockFatalErrorsService.mock.calls[0];
|
||||
const [{ stopCoreSystem }] = FatalErrorsServiceConstructor.mock.calls[0];
|
||||
|
||||
expect(coreSystem.stop).not.toHaveBeenCalled();
|
||||
stopCoreSystem();
|
||||
|
@ -251,75 +198,55 @@ describe('constructor', () => {
|
|||
|
||||
describe('#stop', () => {
|
||||
it('calls legacyPlatform.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
const [legacyPlatformService] = MockLegacyPlatformService.mock.instances;
|
||||
expect(legacyPlatformService.stop).not.toHaveBeenCalled();
|
||||
expect(MockLegacyPlatformService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(legacyPlatformService.stop).toHaveBeenCalled();
|
||||
expect(MockLegacyPlatformService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls notifications.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
const [notificationsService] = MockNotificationsService.mock.instances;
|
||||
expect(notificationsService.stop).not.toHaveBeenCalled();
|
||||
expect(MockNotificationsService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(notificationsService.stop).toHaveBeenCalled();
|
||||
expect(MockNotificationsService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls http.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
|
||||
const [httpService] = MockHttpService.mock.instances;
|
||||
expect(httpService.stop).not.toHaveBeenCalled();
|
||||
const coreSystem = createCoreSystem();
|
||||
expect(MockHttpService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(httpService.stop).toHaveBeenCalled();
|
||||
expect(MockHttpService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls chrome.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
const [chromeService] = MockChromeService.mock.instances;
|
||||
expect(chromeService.stop).not.toHaveBeenCalled();
|
||||
expect(MockChromeService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(chromeService.stop).toHaveBeenCalled();
|
||||
expect(MockChromeService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls uiSettings.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
const [uiSettings] = MockUiSettingsService.mock.instances;
|
||||
expect(uiSettings.stop).not.toHaveBeenCalled();
|
||||
expect(MockUiSettingsService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(uiSettings.stop).toHaveBeenCalled();
|
||||
expect(MockUiSettingsService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls i18n.stop()', () => {
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
});
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
const [i18n] = MockI18nService.mock.instances;
|
||||
expect(i18n.stop).not.toHaveBeenCalled();
|
||||
expect(MockI18nService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(i18n.stop).toHaveBeenCalled();
|
||||
expect(MockI18nService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears the rootDomElement', () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const coreSystem = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
const coreSystem = createCoreSystem({
|
||||
rootDomElement,
|
||||
});
|
||||
|
||||
|
@ -332,8 +259,7 @@ describe('#stop', () => {
|
|||
|
||||
describe('#start()', () => {
|
||||
function startCore(rootDomElement = defaultCoreSystemParams.rootDomElement) {
|
||||
const core = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
const core = createCoreSystem({
|
||||
rootDomElement,
|
||||
});
|
||||
|
||||
|
@ -349,94 +275,60 @@ describe('#start()', () => {
|
|||
|
||||
it('calls injectedMetadata#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockInjectedMetadataService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith();
|
||||
|
||||
expect(MockInjectedMetadataService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls http#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockHttpService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({
|
||||
fatalErrors: mockFatalErrorsStart,
|
||||
});
|
||||
expect(MockHttpService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls basePath#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockBasePathService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({
|
||||
injectedMetadata: mockInjectedMetadataStart,
|
||||
});
|
||||
expect(MockBasePathService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls uiSettings#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockUiSettingsService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({
|
||||
notifications: mockNotificationStart,
|
||||
http: mockHttp,
|
||||
injectedMetadata: mockInjectedMetadataStart,
|
||||
basePath: mockBasePathStart,
|
||||
});
|
||||
expect(MockUiSettingsService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls i18n#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockI18nService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith();
|
||||
expect(MockI18nService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls fatalErrors#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockFatalErrorsService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({ i18n: mockI18nStart });
|
||||
expect(MockFatalErrorsService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls notifications#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockNotificationsService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({ i18n: mockI18nStart });
|
||||
expect(MockNotificationsService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls chrome#start()', () => {
|
||||
startCore();
|
||||
const [mockInstance] = MockChromeService.mock.instances;
|
||||
expect(mockInstance.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockInstance.start).toHaveBeenCalledWith({
|
||||
notifications: mockNotificationStart,
|
||||
injectedMetadata: mockInjectedMetadataStart,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns start contract', () => {
|
||||
expect(startCore()).toEqual({ fatalErrors: mockFatalErrorsStart });
|
||||
expect(MockChromeService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LegacyPlatform targetDomElement', () => {
|
||||
it('only mounts the element when started, before starting the legacyPlatformService', () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const core = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
const core = createCoreSystem({
|
||||
rootDomElement,
|
||||
});
|
||||
|
||||
const [legacyPlatform] = MockLegacyPlatformService.mock.instances;
|
||||
|
||||
let targetDomElementParentInStart: HTMLElement;
|
||||
(legacyPlatform as any).start.mockImplementation(() => {
|
||||
MockLegacyPlatformService.start.mockImplementation(() => {
|
||||
targetDomElementParentInStart = targetDomElement.parentElement;
|
||||
});
|
||||
|
||||
// targetDomElement should not have a parent element when the LegacyPlatformService is constructed
|
||||
const [[{ targetDomElement }]] = MockLegacyPlatformService.mock.calls;
|
||||
const [[{ targetDomElement }]] = LegacyPlatformServiceConstructor.mock.calls;
|
||||
expect(targetDomElement).toHaveProperty('parentElement', null);
|
||||
|
||||
// starting the core system should mount the targetDomElement as a child of the rootDomElement
|
||||
|
@ -448,20 +340,20 @@ describe('LegacyPlatform targetDomElement', () => {
|
|||
describe('Notifications targetDomElement', () => {
|
||||
it('only mounts the element when started, before starting the notificationsService', () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const core = new CoreSystem({
|
||||
...defaultCoreSystemParams,
|
||||
const core = createCoreSystem({
|
||||
rootDomElement,
|
||||
});
|
||||
|
||||
const [notifications] = MockNotificationsService.mock.instances;
|
||||
|
||||
let targetDomElementParentInStart: HTMLElement;
|
||||
(notifications as any).start.mockImplementation(() => {
|
||||
targetDomElementParentInStart = targetDomElement.parentElement;
|
||||
});
|
||||
|
||||
MockNotificationsService.start.mockImplementation(
|
||||
(): any => {
|
||||
targetDomElementParentInStart = targetDomElement.parentElement;
|
||||
}
|
||||
);
|
||||
|
||||
// targetDomElement should not have a parent element when the LegacyPlatformService is constructed
|
||||
const [[{ targetDomElement }]] = MockNotificationsService.mock.calls;
|
||||
const [[{ targetDomElement }]] = NotificationServiceConstructor.mock.calls;
|
||||
expect(targetDomElement).toHaveProperty('parentElement', null);
|
||||
|
||||
// starting the core system should mount the targetDomElement as a child of the rootDomElement
|
||||
|
|
44
src/core/public/fatal_errors/fatal_errors_service.mock.ts
Normal file
44
src/core/public/fatal_errors/fatal_errors_service.mock.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FatalErrorsService, FatalErrorsStart } from './fatal_errors_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<FatalErrorsStart> = {
|
||||
add: jest.fn<never, any>(() => undefined as never),
|
||||
get$: jest.fn(),
|
||||
};
|
||||
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type FatalErrorsServiceContract = PublicMethodsOf<FatalErrorsService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<FatalErrorsServiceContract> = {
|
||||
start: jest.fn(),
|
||||
add: jest.fn<never, any>(() => undefined as never),
|
||||
};
|
||||
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const fatalErrorsServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
42
src/core/public/http/http_service.mock.ts
Normal file
42
src/core/public/http/http_service.mock.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { HttpService, HttpStart } from './http_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<HttpStart> = {
|
||||
addLoadingCount: jest.fn(),
|
||||
getLoadingCount$: jest.fn(),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type HttpServiceContract = PublicMethodsOf<HttpService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<HttpServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const httpServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -20,13 +20,12 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import { toArray } from 'rxjs/operators';
|
||||
|
||||
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
|
||||
import { HttpService } from './http_service';
|
||||
|
||||
function setup() {
|
||||
const service = new HttpService();
|
||||
const fatalErrors: any = {
|
||||
add: jest.fn(),
|
||||
};
|
||||
const fatalErrors = fatalErrorsServiceMock.createStartContract();
|
||||
const start = service.start({ fatalErrors });
|
||||
|
||||
return { service, fatalErrors, start };
|
||||
|
|
|
@ -16,29 +16,26 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { I18nService, I18nStart } from './i18n_service';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { uiModules } from './modules';
|
||||
const NL_RE = /\n/g;
|
||||
const events = 'keydown keypress keyup change';
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<I18nStart> = {
|
||||
Context: jest.fn(),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
uiModules.get('kibana')
|
||||
.directive('elasticTextarea', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, $el) {
|
||||
type I18nServiceContract = PublicMethodsOf<I18nService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<I18nServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
function resize() {
|
||||
$el.attr('rows', _.size($el.val().match(NL_RE)) + 1);
|
||||
}
|
||||
|
||||
$el.on(events, resize);
|
||||
$scope.$evalAsync(resize);
|
||||
$scope.$on('$destroy', function () {
|
||||
$el.off(events, resize);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
export const i18nServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { InjectedMetadataService, InjectedMetadataStart } from './injected_metadata_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<InjectedMetadataStart> = {
|
||||
getBasePath: jest.fn(),
|
||||
getKibanaVersion: jest.fn(),
|
||||
getCspConfig: jest.fn(),
|
||||
getLegacyMetadata: jest.fn(),
|
||||
getInjectedVar: jest.fn(),
|
||||
getInjectedVars: jest.fn(),
|
||||
};
|
||||
startContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
|
||||
startContract.getKibanaVersion.mockReturnValue('kibanaVersion');
|
||||
startContract.getLegacyMetadata.mockReturnValue({
|
||||
uiSettings: {
|
||||
defaults: { legacyInjectedUiSettingDefaults: true },
|
||||
user: { legacyInjectedUiSettingUserValues: true },
|
||||
},
|
||||
} as any);
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type InjectedMetadataServiceContract = PublicMethodsOf<InjectedMetadataService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<InjectedMetadataServiceContract> = {
|
||||
start: jest.fn(),
|
||||
getKibanaVersion: jest.fn(),
|
||||
getKibanaBuildNumber: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const injectedMetadataServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -40,7 +40,7 @@ Array [
|
|||
]
|
||||
`;
|
||||
|
||||
exports[`#stop() destroys the angular scope and empties the targetDomElement if angular is bootstraped to targetDomElement 1`] = `
|
||||
exports[`#stop() destroys the angular scope and empties the targetDomElement if angular is bootstrapped to targetDomElement 1`] = `
|
||||
<div
|
||||
class="ng-scope"
|
||||
/>
|
||||
|
|
|
@ -16,19 +16,17 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { LegacyPlatformService } from './legacy_service';
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
type LegacyPlatformServiceContract = PublicMethodsOf<LegacyPlatformService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<LegacyPlatformServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
module.directive('focusOn', ($timeout) => ({
|
||||
restrict: 'A',
|
||||
link: function (scope, elem, attrs) {
|
||||
scope.$on(attrs.focusOn, () => {
|
||||
$timeout(() => {
|
||||
return elem.find('input,select')
|
||||
.addBack('input,select')
|
||||
.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
}));
|
||||
export const legacyPlatformServiceMock = {
|
||||
create: createMock,
|
||||
};
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
const mockLoadOrder: string[] = [];
|
||||
|
||||
|
@ -142,34 +141,24 @@ jest.mock('ui/chrome/services/global_nav_state', () => {
|
|||
};
|
||||
});
|
||||
|
||||
import { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { chromeServiceMock } from '../chrome/chrome_service.mock';
|
||||
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { i18nServiceMock } from '../i18n/i18n_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
|
||||
import { LegacyPlatformService } from './legacy_service';
|
||||
|
||||
const fatalErrorsStart = {} as any;
|
||||
const notificationsStart = {
|
||||
toasts: {},
|
||||
} as any;
|
||||
|
||||
const injectedMetadataStart: any = {
|
||||
getBasePath: jest.fn(),
|
||||
getLegacyMetadata: jest.fn(),
|
||||
};
|
||||
|
||||
const httpStart = {
|
||||
addLoadingCount: jest.fn(),
|
||||
getLoadingCount$: jest
|
||||
.fn()
|
||||
.mockImplementation(() => new Rx.Observable(observer => observer.next(0))),
|
||||
};
|
||||
|
||||
const basePathStart = {
|
||||
get: jest.fn(),
|
||||
addToPath: jest.fn(),
|
||||
removeFromPath: jest.fn(),
|
||||
};
|
||||
|
||||
const uiSettingsStart: any = {};
|
||||
const chromeStart: any = {};
|
||||
const i18nStart: any = { Context: () => '' };
|
||||
const basePathStart = basePathServiceMock.createStartContract();
|
||||
const chromeStart = chromeServiceMock.createStartContract();
|
||||
const fatalErrorsStart = fatalErrorsServiceMock.createStartContract();
|
||||
const httpStart = httpServiceMock.createStartContract();
|
||||
const i18nStart = i18nServiceMock.createStartContract();
|
||||
const injectedMetadataStart = injectedMetadataServiceMock.createStartContract();
|
||||
const notificationsStart = notificationServiceMock.createStartContract();
|
||||
const uiSettingsStart = uiSettingsServiceMock.createStartContract();
|
||||
|
||||
const defaultParams = {
|
||||
targetDomElement: document.createElement('div'),
|
||||
|
@ -200,7 +189,7 @@ describe('#start()', () => {
|
|||
describe('default', () => {
|
||||
it('passes legacy metadata from injectedVars to ui/metadata', () => {
|
||||
const legacyMetadata = { isLegacyMetadata: true };
|
||||
injectedMetadataStart.getLegacyMetadata.mockReturnValue(legacyMetadata);
|
||||
injectedMetadataStart.getLegacyMetadata.mockReturnValue(legacyMetadata as any);
|
||||
|
||||
const legacyPlatform = new LegacyPlatformService({
|
||||
...defaultParams,
|
||||
|
@ -422,7 +411,7 @@ describe('#stop()', () => {
|
|||
expect(targetDomElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('destroys the angular scope and empties the targetDomElement if angular is bootstraped to targetDomElement', () => {
|
||||
it('destroys the angular scope and empties the targetDomElement if angular is bootstrapped to targetDomElement', () => {
|
||||
const targetDomElement = document.createElement('div');
|
||||
const scopeDestroySpy = jest.fn();
|
||||
|
||||
|
@ -431,7 +420,7 @@ describe('#stop()', () => {
|
|||
targetDomElement,
|
||||
});
|
||||
|
||||
// simulate bootstraping with a module "foo"
|
||||
// simulate bootstrapping with a module "foo"
|
||||
angular.module('foo', []).directive('bar', () => ({
|
||||
restrict: 'E',
|
||||
link($scope) {
|
||||
|
|
28
src/core/public/mocks.ts
Normal file
28
src/core/public/mocks.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { basePathServiceMock } from './base_path/base_path_service.mock';
|
||||
export { chromeServiceMock } from './chrome/chrome_service.mock';
|
||||
export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
|
||||
export { httpServiceMock } from './http/http_service.mock';
|
||||
export { i18nServiceMock } from './i18n/i18n_service.mock';
|
||||
export { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock';
|
||||
export { legacyPlatformServiceMock } from './legacy/legacy_service.mock';
|
||||
export { notificationServiceMock } from './notifications/notifications_service.mock';
|
||||
export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
44
src/core/public/notifications/notifications_service.mock.ts
Normal file
44
src/core/public/notifications/notifications_service.mock.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { NotificationsService, NotificationsStart } from './notifications_service';
|
||||
import { toastsServiceMock } from './toasts/toasts_service.mock';
|
||||
import { ToastsStart } from './toasts/toasts_start';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<NotificationsStart> = {
|
||||
// we have to suppress type errors until decide how to mock es6 class
|
||||
toasts: (toastsServiceMock.createStartContract() as unknown) as ToastsStart,
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type NotificationsServiceContract = PublicMethodsOf<NotificationsService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<NotificationsServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const notificationServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
35
src/core/public/notifications/toasts/toasts_service.mock.ts
Normal file
35
src/core/public/notifications/toasts/toasts_service.mock.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ToastsStart } from './toasts_start';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<PublicMethodsOf<ToastsStart>> = {
|
||||
get$: jest.fn(),
|
||||
add: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
addDanger: jest.fn(),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
export const toastsServiceMock = {
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -5,7 +5,9 @@ exports[`#start constructs UiSettingsClient and UiSettingsApi: UiSettingsApi arg
|
|||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"basePathStart": true,
|
||||
"addToPath": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"removeFromPath": [MockFunction],
|
||||
},
|
||||
"kibanaVersion",
|
||||
],
|
||||
|
|
|
@ -22,12 +22,12 @@ import fetchMock from 'fetch-mock/es5/client';
|
|||
import * as Rx from 'rxjs';
|
||||
import { takeUntil, toArray } from 'rxjs/operators';
|
||||
|
||||
import { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { UiSettingsApi } from './ui_settings_api';
|
||||
|
||||
function setup() {
|
||||
const basePath: any = {
|
||||
addToPath: jest.fn(path => `/foo/bar${path}`),
|
||||
};
|
||||
const basePath = basePathServiceMock.createStartContract();
|
||||
basePath.addToPath.mockImplementation(path => `/foo/bar${path}`);
|
||||
|
||||
const uiSettingsApi = new UiSettingsApi(basePath, 'v9.9.9');
|
||||
|
||||
|
|
55
src/core/public/ui_settings/ui_settings_service.mock.ts
Normal file
55
src/core/public/ui_settings/ui_settings_service.mock.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { UiSettingsService, UiSettingsStart } from './ui_settings_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<PublicMethodsOf<UiSettingsStart>> = {
|
||||
getAll: jest.fn(),
|
||||
get: jest.fn(),
|
||||
get$: jest.fn(),
|
||||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
isDeclared: jest.fn(),
|
||||
isDefault: jest.fn(),
|
||||
isCustom: jest.fn(),
|
||||
isOverridden: jest.fn(),
|
||||
overrideLocalDefault: jest.fn(),
|
||||
getUpdate$: jest.fn(),
|
||||
getSaved$: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
// we have to suppress type errors until decide how to mock es6 class
|
||||
return (startContract as unknown) as UiSettingsStart;
|
||||
};
|
||||
|
||||
type UiSettingsServiceContract = PublicMethodsOf<UiSettingsService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<UiSettingsServiceContract> = {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const uiSettingsServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -56,31 +56,19 @@ const MockUiSettingsClient = mockClass('./ui_settings_client', UiSettingsClient,
|
|||
inst.stop = jest.fn();
|
||||
});
|
||||
|
||||
// Load the service
|
||||
import { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
import { UiSettingsService } from './ui_settings_service';
|
||||
|
||||
const httpStart = {
|
||||
addLoadingCount: jest.fn(),
|
||||
};
|
||||
const httpStart = httpServiceMock.createStartContract();
|
||||
|
||||
const defaultDeps: any = {
|
||||
notifications: {
|
||||
notificationsStart: true,
|
||||
},
|
||||
const defaultDeps = {
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
http: httpStart,
|
||||
injectedMetadata: {
|
||||
injectedMetadataStart: true,
|
||||
getKibanaVersion: jest.fn().mockReturnValue('kibanaVersion'),
|
||||
getLegacyMetadata: jest.fn().mockReturnValue({
|
||||
uiSettings: {
|
||||
defaults: { legacyInjectedUiSettingDefaults: true },
|
||||
user: { legacyInjectedUiSettingUserValues: true },
|
||||
},
|
||||
}),
|
||||
},
|
||||
basePath: {
|
||||
basePathStart: true,
|
||||
},
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
basePath: basePathServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -21,12 +21,6 @@ import { ObjectToConfigAdapter } from './object_to_config_adapter';
|
|||
|
||||
import { ConfigService } from './config_service';
|
||||
|
||||
type MethodKeysOf<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
|
||||
}[keyof T];
|
||||
|
||||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
||||
type ConfigSericeContract = PublicMethodsOf<ConfigService>;
|
||||
const createConfigServiceMock = () => {
|
||||
const mocked: jest.Mocked<ConfigSericeContract> = {
|
||||
|
|
|
@ -34,12 +34,6 @@ const createStartContractMock = () => {
|
|||
return startContract;
|
||||
};
|
||||
|
||||
type MethodKeysOf<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
|
||||
}[keyof T];
|
||||
|
||||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
||||
type ElasticsearchServiceContract = PublicMethodsOf<ElasticsearchService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<ElasticsearchServiceContract> = {
|
||||
|
|
|
@ -28,12 +28,6 @@ const createStartContractMock = () => {
|
|||
return startContract;
|
||||
};
|
||||
|
||||
type MethodKeysOf<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
|
||||
}[keyof T];
|
||||
|
||||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
||||
type HttpSericeContract = PublicMethodsOf<HttpService>;
|
||||
const createHttpServiceMock = () => {
|
||||
const mocked: jest.Mocked<HttpSericeContract> = {
|
||||
|
|
|
@ -21,12 +21,6 @@
|
|||
import { Logger } from './logger';
|
||||
import { LoggingService } from './logging_service';
|
||||
|
||||
type MethodKeysOf<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
|
||||
}[keyof T];
|
||||
|
||||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
||||
type LoggingServiceContract = PublicMethodsOf<LoggingService>;
|
||||
type MockedLogger = jest.Mocked<Logger>;
|
||||
|
||||
|
|
23
src/core/server/mocks.ts
Normal file
23
src/core/server/mocks.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { configServiceMock } from './config/config_service.mock';
|
||||
export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
|
||||
export { httpServiceMock } from './http/http_service.mock';
|
||||
export { loggingServiceMock } from './logging/logging_service.mock';
|
|
@ -120,7 +120,6 @@ export const TEMPORARILY_IGNORED_PATHS = [
|
|||
'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js',
|
||||
'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json',
|
||||
'src/fixtures/vislib/mock_data/terms/_seriesMultiple.js',
|
||||
'src/legacy/ui/public/angular-bootstrap/accordion/accordion-group.html',
|
||||
'src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js',
|
||||
'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html',
|
||||
'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html',
|
||||
|
@ -138,7 +137,6 @@ export const TEMPORARILY_IGNORED_PATHS = [
|
|||
'src/legacy/ui/public/assets/favicons/mstile-310x150.png',
|
||||
'src/legacy/ui/public/assets/favicons/mstile-310x310.png',
|
||||
'src/legacy/ui/public/assets/favicons/safari-pinned-tab.svg',
|
||||
'src/legacy/ui/public/directives/__tests__/confirm-click.js',
|
||||
'src/legacy/ui/public/styles/bootstrap/component-animations.less',
|
||||
'src/legacy/ui/public/styles/bootstrap/input-groups.less',
|
||||
'src/legacy/ui/public/styles/bootstrap/list-group.less',
|
||||
|
|
|
@ -132,6 +132,9 @@ export default function (kibana) {
|
|||
'kql-telemetry': {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
'sample-data-telemetry': {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
},
|
||||
|
||||
injectDefaultVars(server, options) {
|
||||
|
|
|
@ -183,5 +183,15 @@
|
|||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sample-data-telemetry": {
|
||||
"properties": {
|
||||
"installCount": {
|
||||
"type": "long"
|
||||
},
|
||||
"unInstallCount": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { createAction } from 'redux-actions';
|
||||
import { KibanaAction } from '../../selectors/types';
|
||||
import { PanelId, PanelsMap, PanelState } from '../selectors';
|
||||
import { PanelId, PanelState, PanelStateMap } from '../selectors';
|
||||
|
||||
export enum PanelActionTypeKeys {
|
||||
DELETE_PANEL = 'DELETE_PANEL',
|
||||
|
@ -37,7 +37,7 @@ export interface UpdatePanelAction
|
|||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANEL, PanelState> {}
|
||||
|
||||
export interface UpdatePanelsAction
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, PanelsMap> {}
|
||||
extends KibanaAction<PanelActionTypeKeys.UPDATE_PANELS, PanelStateMap> {}
|
||||
|
||||
export interface ResetPanelTitleAction
|
||||
extends KibanaAction<PanelActionTypeKeys.RESET_PANEl_TITLE, PanelId> {}
|
||||
|
@ -50,7 +50,8 @@ export interface SetPanelTitleActionPayload {
|
|||
export interface SetPanelTitleAction
|
||||
extends KibanaAction<PanelActionTypeKeys.SET_PANEl_TITLE, SetPanelTitleActionPayload> {}
|
||||
|
||||
export interface SetPanelsAction extends KibanaAction<PanelActionTypeKeys.SET_PANELS, PanelsMap> {}
|
||||
export interface SetPanelsAction
|
||||
extends KibanaAction<PanelActionTypeKeys.SET_PANELS, PanelStateMap> {}
|
||||
|
||||
export type PanelActions =
|
||||
| DeletePanelAction
|
||||
|
@ -66,5 +67,5 @@ export const resetPanelTitle = createAction<PanelId>(PanelActionTypeKeys.RESET_P
|
|||
export const setPanelTitle = createAction<SetPanelTitleActionPayload>(
|
||||
PanelActionTypeKeys.SET_PANEl_TITLE
|
||||
);
|
||||
export const updatePanels = createAction<PanelsMap>(PanelActionTypeKeys.UPDATE_PANELS);
|
||||
export const setPanels = createAction<PanelsMap>(PanelActionTypeKeys.SET_PANELS);
|
||||
export const updatePanels = createAction<PanelStateMap>(PanelActionTypeKeys.UPDATE_PANELS);
|
||||
export const setPanels = createAction<PanelStateMap>(PanelActionTypeKeys.SET_PANELS);
|
||||
|
|
|
@ -20,20 +20,20 @@
|
|||
import _ from 'lodash';
|
||||
import { Reducer } from 'redux';
|
||||
import { PanelActions, PanelActionTypeKeys, SetPanelTitleActionPayload } from '../actions';
|
||||
import { PanelId, PanelsMap, PanelState } from '../selectors';
|
||||
import { PanelId, PanelState, PanelStateMap } from '../selectors';
|
||||
|
||||
const deletePanel = (panels: PanelsMap, panelId: PanelId): PanelsMap => {
|
||||
const deletePanel = (panels: PanelStateMap, panelId: PanelId): PanelStateMap => {
|
||||
const panelsCopy = { ...panels };
|
||||
delete panelsCopy[panelId];
|
||||
return panelsCopy;
|
||||
};
|
||||
|
||||
const updatePanel = (panels: PanelsMap, panelState: PanelState): PanelsMap => ({
|
||||
const updatePanel = (panels: PanelStateMap, panelState: PanelState): PanelStateMap => ({
|
||||
...panels,
|
||||
[panelState.panelIndex]: panelState,
|
||||
});
|
||||
|
||||
const updatePanels = (panels: PanelsMap, updatedPanels: PanelsMap): PanelsMap => {
|
||||
const updatePanels = (panels: PanelStateMap, updatedPanels: PanelStateMap): PanelStateMap => {
|
||||
const panelsCopy = { ...panels };
|
||||
Object.values(updatedPanels).forEach(panel => {
|
||||
panelsCopy[panel.panelIndex] = panel;
|
||||
|
@ -41,7 +41,7 @@ const updatePanels = (panels: PanelsMap, updatedPanels: PanelsMap): PanelsMap =>
|
|||
return panelsCopy;
|
||||
};
|
||||
|
||||
const resetPanelTitle = (panels: PanelsMap, panelId: PanelId) => ({
|
||||
const resetPanelTitle = (panels: PanelStateMap, panelId: PanelId) => ({
|
||||
...panels,
|
||||
[panelId]: {
|
||||
...panels[panelId],
|
||||
|
@ -49,7 +49,7 @@ const resetPanelTitle = (panels: PanelsMap, panelId: PanelId) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const setPanelTitle = (panels: PanelsMap, payload: SetPanelTitleActionPayload) => ({
|
||||
const setPanelTitle = (panels: PanelStateMap, payload: SetPanelTitleActionPayload) => ({
|
||||
...panels,
|
||||
[payload.panelId]: {
|
||||
...panels[payload.panelId],
|
||||
|
@ -57,9 +57,9 @@ const setPanelTitle = (panels: PanelsMap, payload: SetPanelTitleActionPayload) =
|
|||
},
|
||||
});
|
||||
|
||||
const setPanels = (panels: PanelsMap, newPanels: PanelsMap) => _.cloneDeep(newPanels);
|
||||
const setPanels = (panels: PanelStateMap, newPanels: PanelStateMap) => _.cloneDeep(newPanels);
|
||||
|
||||
export const panelsReducer: Reducer<PanelsMap> = (panels = {}, action): PanelsMap => {
|
||||
export const panelsReducer: Reducer<PanelStateMap> = (panels = {}, action): PanelStateMap => {
|
||||
switch ((action as PanelActions).type) {
|
||||
case PanelActionTypeKeys.DELETE_PANEL:
|
||||
return deletePanel(panels, action.payload);
|
||||
|
|
|
@ -34,11 +34,11 @@ import {
|
|||
EmbeddableReduxState,
|
||||
EmbeddablesMap,
|
||||
PanelId,
|
||||
PanelsMap,
|
||||
PanelState,
|
||||
PanelStateMap,
|
||||
} from './types';
|
||||
|
||||
export const getPanels = (dashboard: DashboardState): PanelsMap => dashboard.panels;
|
||||
export const getPanels = (dashboard: DashboardState): PanelStateMap => dashboard.panels;
|
||||
|
||||
export const getPanel = (dashboard: DashboardState, panelId: PanelId): PanelState =>
|
||||
getPanels(dashboard)[panelId];
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface EmbeddableReduxState {
|
|||
readonly lastReloadRequestTime: number;
|
||||
}
|
||||
|
||||
export interface PanelsMap {
|
||||
export interface PanelStateMap {
|
||||
readonly [panelId: string]: PanelState;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ export interface DashboardMetadata {
|
|||
|
||||
export interface DashboardState {
|
||||
readonly view: ViewState;
|
||||
readonly panels: PanelsMap;
|
||||
readonly panels: PanelStateMap;
|
||||
readonly embeddables: EmbeddablesMap;
|
||||
readonly metadata: DashboardMetadata;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import { management } from 'ui/management';
|
|||
import './_view';
|
||||
import './_objects';
|
||||
import 'ace';
|
||||
import 'ui/directives/confirm_click';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// While we are on a small screen the visualization is below the
|
||||
// editor. In this cases it needs a minimum height, since it would otherwise
|
||||
// maybe end up with 0 height since it just gets the flexbox rest of the screen.
|
||||
min-height: 400px;
|
||||
min-height: $euiSizeL * 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { EuiToolTip, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isBoolean } from 'lodash';
|
||||
|
||||
function AddDeleteButtons(props) {
|
||||
const { testSubj } = props;
|
||||
|
@ -59,43 +60,85 @@ function AddDeleteButtons(props) {
|
|||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
const createClone = () => {
|
||||
let cloneBtn = null;
|
||||
|
||||
if (props.onClone && !props.disableAdd) {
|
||||
cloneBtn = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={props.cloneTooltip}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`${testSubj}CloneBtn`}
|
||||
aria-label={props.cloneTooltip}
|
||||
iconType="copy"
|
||||
onClick={props.onClone}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
return cloneBtn;
|
||||
};
|
||||
|
||||
const createActivatePanel = () => {
|
||||
let activatePanelBtn = null;
|
||||
|
||||
if (isBoolean(props.isPanelActive)) {
|
||||
const tooltip = props.isPanelActive ? props.deactivatePanelTooltip : props.activatePanelTooltip;
|
||||
const iconType = props.isPanelActive ? 'eye' : 'eyeClosed';
|
||||
|
||||
activatePanelBtn = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={tooltip} >
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`${testSubj}ActivatePanelBtn`}
|
||||
aria-label={tooltip}
|
||||
iconType={iconType}
|
||||
onClick={props.togglePanelActivation}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
return activatePanelBtn;
|
||||
};
|
||||
|
||||
const deleteBtn = createDelete();
|
||||
const addBtn = createAdd();
|
||||
let clone;
|
||||
if (props.onClone && !props.disableAdd) {
|
||||
clone = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={props.cloneTooltip}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`${testSubj}CloneBtn`}
|
||||
aria-label={props.cloneTooltip}
|
||||
iconType="copy"
|
||||
onClick={props.onClone}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
const cloneBtn = createClone();
|
||||
const activatePanelBtn = createActivatePanel();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" responsive={props.responsive} justifyContent="flexEnd">
|
||||
{ clone }
|
||||
{ addBtn }
|
||||
{ deleteBtn }
|
||||
{activatePanelBtn}
|
||||
{cloneBtn}
|
||||
{addBtn}
|
||||
{deleteBtn}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
AddDeleteButtons.defaultProps = {
|
||||
testSubj: 'Add',
|
||||
activeTooltip: i18n.translate('tsvb.addDeleteButtons.addButtonDefaultTooltip', { defaultMessage: 'Add' }),
|
||||
addTooltip: i18n.translate('tsvb.addDeleteButtons.addButtonDefaultTooltip', { defaultMessage: 'Add' }),
|
||||
deleteTooltip: i18n.translate('tsvb.addDeleteButtons.deleteButtonDefaultTooltip', { defaultMessage: 'Delete' }),
|
||||
cloneTooltip: i18n.translate('tsvb.addDeleteButtons.cloneButtonDefaultTooltip', { defaultMessage: 'Clone' })
|
||||
cloneTooltip: i18n.translate('tsvb.addDeleteButtons.cloneButtonDefaultTooltip', { defaultMessage: 'Clone' }),
|
||||
activatePanelTooltip: i18n.translate('tsvb.addDeleteButtons.reEnableTooltip', { defaultMessage: 'Re-enable' }),
|
||||
deactivatePanelTooltip: i18n.translate('tsvb.addDeleteButtons.temporarilyDisableTooltip', { defaultMessage: 'Temporarily Disable' }),
|
||||
};
|
||||
|
||||
AddDeleteButtons.propTypes = {
|
||||
addTooltip: PropTypes.string,
|
||||
deleteTooltip: PropTypes.string,
|
||||
cloneTooltip: PropTypes.string,
|
||||
activatePanelTooltip: PropTypes.string,
|
||||
deactivatePanelTooltip: PropTypes.string,
|
||||
togglePanelActivation: PropTypes.func,
|
||||
isPanelActive: PropTypes.bool,
|
||||
disableAdd: PropTypes.bool,
|
||||
disableDelete: PropTypes.bool,
|
||||
onClone: PropTypes.func,
|
||||
|
|
|
@ -78,6 +78,11 @@ class AnnotationsEditor extends Component {
|
|||
const fn = collectionActions.handleChange.bind(null, this.props);
|
||||
fn(_.assign({}, model, part));
|
||||
};
|
||||
const togglePanelActivation = () => {
|
||||
handleChange({
|
||||
hidden: !model.hidden,
|
||||
});
|
||||
};
|
||||
const htmlId = htmlIdGenerator(model.id);
|
||||
const handleAdd = collectionActions.handleAdd
|
||||
.bind(null, this.props, newAnnotation);
|
||||
|
@ -249,6 +254,8 @@ class AnnotationsEditor extends Component {
|
|||
<AddDeleteButtons
|
||||
onAdd={handleAdd}
|
||||
onDelete={handleDelete}
|
||||
togglePanelActivation={togglePanelActivation}
|
||||
isPanelActive={!model.hidden}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -36,38 +36,43 @@ const lookup = {
|
|||
metric,
|
||||
timeseries,
|
||||
gauge,
|
||||
markdown
|
||||
markdown,
|
||||
};
|
||||
|
||||
class Series extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
visible: true,
|
||||
selectedTab: 'metrics'
|
||||
selectedTab: 'metrics',
|
||||
};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.switchTab = this.switchTab.bind(this);
|
||||
this.toggleVisible = this.toggleVisible.bind(this);
|
||||
}
|
||||
|
||||
switchTab(selectedTab) {
|
||||
switchTab = (selectedTab) => {
|
||||
this.setState({ selectedTab });
|
||||
}
|
||||
};
|
||||
|
||||
handleChange(part) {
|
||||
handleChange = (part) => {
|
||||
if (this.props.onChange) {
|
||||
const { model } = this.props;
|
||||
const doc = _.assign({}, model, part);
|
||||
this.props.onChange(doc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggleVisible(e) {
|
||||
togglePanelActivation = () => {
|
||||
const { model } = this.props;
|
||||
|
||||
this.handleChange({
|
||||
hidden: !model.hidden,
|
||||
});
|
||||
};
|
||||
|
||||
toggleVisible = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ visible: !this.state.visible });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { panel } = this.props;
|
||||
|
@ -95,7 +100,8 @@ class Series extends Component {
|
|||
style: this.props.style,
|
||||
switchTab: this.switchTab,
|
||||
toggleVisible: this.toggleVisible,
|
||||
visible: this.state.visible
|
||||
togglePanelActivation: this.togglePanelActivation,
|
||||
visible: this.state.visible,
|
||||
};
|
||||
return (<Component {...params}/>);
|
||||
}
|
||||
|
@ -113,7 +119,7 @@ class Series extends Component {
|
|||
}
|
||||
|
||||
Series.defaultProps = {
|
||||
name: 'metrics'
|
||||
name: 'metrics',
|
||||
};
|
||||
|
||||
Series.propTypes = {
|
||||
|
|
|
@ -226,7 +226,7 @@ GaugeSeriesUi.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
};
|
||||
|
||||
const GaugeSeries = injectI18n(GaugeSeriesUi);
|
||||
|
|
|
@ -48,6 +48,7 @@ function getColors(props) {
|
|||
function GaugeVisualization(props) {
|
||||
const { backgroundColor, model, visData } = props;
|
||||
const colors = getColors(props);
|
||||
|
||||
const series = _.get(visData, `${model.id}.series`, [])
|
||||
.filter(row => row)
|
||||
.map((row, i) => {
|
||||
|
|
|
@ -196,7 +196,7 @@ MarkdownSeriesUi.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
};
|
||||
|
||||
const MarkdownSeries = injectI18n(MarkdownSeriesUi);
|
||||
|
|
|
@ -193,6 +193,8 @@ function MetricSeriesUi(props) {
|
|||
onDelete={onDelete}
|
||||
onClone={props.onClone}
|
||||
onAdd={onAdd}
|
||||
togglePanelActivation={props.togglePanelActivation}
|
||||
isPanelActive={!model.hidden}
|
||||
disableDelete={disableDelete}
|
||||
disableAdd={disableAdd}
|
||||
responsive={false}
|
||||
|
@ -228,7 +230,8 @@ MetricSeriesUi.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
togglePanelActivation: PropTypes.func,
|
||||
};
|
||||
|
||||
const MetricSeries = injectI18n(MetricSeriesUi);
|
||||
|
|
|
@ -165,6 +165,8 @@ function TableSeries(props) {
|
|||
onDelete={onDelete}
|
||||
onClone={props.onClone}
|
||||
onAdd={onAdd}
|
||||
togglePanelActivation={props.togglePanelActivation}
|
||||
isPanelActive={!model.hidden}
|
||||
disableDelete={disableDelete}
|
||||
disableAdd={disableAdd}
|
||||
responsive={false}
|
||||
|
@ -198,7 +200,8 @@ TableSeries.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
togglePanelActivation: PropTypes.func,
|
||||
};
|
||||
|
||||
export default injectI18n(TableSeries);
|
||||
|
|
|
@ -57,6 +57,12 @@ class TableVis extends Component {
|
|||
this.dateFormatter = new DateFormat({}, this.props.getConfig);
|
||||
}
|
||||
|
||||
get visibleSeries() {
|
||||
return _
|
||||
.get(this.props, 'model.series', [])
|
||||
.filter(series => !series.hidden);
|
||||
}
|
||||
|
||||
renderRow = row => {
|
||||
const { model } = this.props;
|
||||
let rowDisplay = model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key;
|
||||
|
@ -65,7 +71,7 @@ class TableVis extends Component {
|
|||
rowDisplay = (<a href={url}>{rowDisplay}</a>);
|
||||
}
|
||||
const columns = row.series.filter(item => item).map(item => {
|
||||
const column = model.series.find(c => c.id === item.id);
|
||||
const column = this.visibleSeries.find(c => c.id === item.id);
|
||||
if (!column) return null;
|
||||
const formatter = tickFormatter(column.formatter, column.value_template, this.props.getConfig);
|
||||
const value = formatter(item.last);
|
||||
|
@ -74,14 +80,14 @@ class TableVis extends Component {
|
|||
const trendIcon = item.slope > 0 ? 'sortUp' : 'sortDown';
|
||||
trend = (
|
||||
<span>
|
||||
<EuiIcon type={trendIcon} color="subdued" />
|
||||
<EuiIcon type={trendIcon} color="subdued"/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const style = { color: getColor(column.color_rules, 'text', item.last) };
|
||||
return (
|
||||
<td key={`${row.key}-${item.id}`} data-test-subj="tvbTableVis__value" className="eui-textRight" style={style}>
|
||||
<span>{ value }</span>
|
||||
<span>{value}</span>
|
||||
{trend}
|
||||
</td>
|
||||
);
|
||||
|
@ -92,16 +98,17 @@ class TableVis extends Component {
|
|||
{columns}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderHeader() {
|
||||
const { model, uiState, onUiState } = this.props;
|
||||
const stateKey = `${model.type}.sort`;
|
||||
const sort = uiState.get(stateKey, {
|
||||
column: '_default_',
|
||||
order: 'asc'
|
||||
order: 'asc',
|
||||
});
|
||||
const columns = model.series.map(item => {
|
||||
|
||||
const columns = this.visibleSeries.map(item => {
|
||||
const metric = _.last(item.metrics);
|
||||
const label = metric.type === 'percentile' ?
|
||||
getPercentileLabel(metric, item) :
|
||||
|
@ -125,7 +132,7 @@ class TableVis extends Component {
|
|||
sortIcon = 'empty';
|
||||
}
|
||||
sortComponent = (
|
||||
<EuiIcon type={sortIcon} />
|
||||
<EuiIcon type={sortIcon}/>
|
||||
);
|
||||
}
|
||||
let headerContent = (
|
||||
|
@ -162,7 +169,7 @@ class TableVis extends Component {
|
|||
sortIcon = 'empty';
|
||||
}
|
||||
const sortComponent = (
|
||||
<EuiIcon type={sortIcon} />
|
||||
<EuiIcon type={sortIcon}/>
|
||||
);
|
||||
const handleSortClick = () => {
|
||||
let order;
|
||||
|
@ -176,7 +183,7 @@ class TableVis extends Component {
|
|||
return (
|
||||
<tr>
|
||||
<th className="eui-textLeft" scope="col" onClick={handleSortClick}>{label} {sortComponent}</th>
|
||||
{ columns }
|
||||
{columns}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
@ -201,14 +208,14 @@ class TableVis extends Component {
|
|||
rows = (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={model.series.length + 1}
|
||||
colSpan={this.visibleSeries.length + 1}
|
||||
>
|
||||
{message}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return(
|
||||
return (
|
||||
<div className="tvbVis" data-test-subj="tableView">
|
||||
<table className="table">
|
||||
<thead>
|
||||
|
@ -225,7 +232,7 @@ class TableVis extends Component {
|
|||
}
|
||||
|
||||
TableVis.defaultProps = {
|
||||
sort: {}
|
||||
sort: {},
|
||||
};
|
||||
|
||||
TableVis.propTypes = {
|
||||
|
@ -236,7 +243,7 @@ TableVis.propTypes = {
|
|||
onUiState: PropTypes.func,
|
||||
uiState: PropTypes.object,
|
||||
pageNumber: PropTypes.number,
|
||||
getConfig: PropTypes.func
|
||||
getConfig: PropTypes.func,
|
||||
};
|
||||
|
||||
export default TableVis;
|
||||
|
|
|
@ -195,6 +195,8 @@ const TimeseriesSeries = injectI18n(function (props) {
|
|||
cloneTooltip={intl.formatMessage({ id: 'tsvb.timeSeries.cloneSeriesTooltip', defaultMessage: 'Clone Series' })}
|
||||
onDelete={onDelete}
|
||||
onClone={props.onClone}
|
||||
togglePanelActivation={props.togglePanelActivation}
|
||||
isPanelActive={!model.hidden}
|
||||
onAdd={onAdd}
|
||||
disableDelete={disableDelete}
|
||||
disableAdd={disableAdd}
|
||||
|
@ -232,7 +234,8 @@ TimeseriesSeries.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
togglePanelActivation: PropTypes.func,
|
||||
};
|
||||
|
||||
export default injectI18n(TimeseriesSeries);
|
||||
|
|
|
@ -188,6 +188,8 @@ const TopNSeries = injectI18n(function (props) {
|
|||
onDelete={onDelete}
|
||||
onClone={props.onClone}
|
||||
onAdd={onAdd}
|
||||
togglePanelActivation={props.togglePanelActivation}
|
||||
isPanelActive={!model.hidden}
|
||||
disableDelete={disableDelete}
|
||||
disableAdd={disableAdd}
|
||||
responsive={false}
|
||||
|
@ -222,7 +224,8 @@ TopNSeries.propTypes = {
|
|||
style: PropTypes.object,
|
||||
switchTab: PropTypes.func,
|
||||
toggleVisible: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
togglePanelActivation: PropTypes.func,
|
||||
};
|
||||
|
||||
export default TopNSeries;
|
||||
|
|
|
@ -24,7 +24,8 @@ function validAnnotation(annotation) {
|
|||
annotation.time_field &&
|
||||
annotation.fields &&
|
||||
annotation.icon &&
|
||||
annotation.template;
|
||||
annotation.template &&
|
||||
!annotation.hidden;
|
||||
}
|
||||
|
||||
export async function getAnnotations(req, panel, esQueryConfig, searchStrategy, capabilities) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import handleErrorResponse from './handle_error_response';
|
|||
import { getAnnotations } from './get_annotations';
|
||||
import { SearchStrategiesRegister } from '../search_strategies/search_strategies_register';
|
||||
import { getEsQueryConfig } from './helpers/get_es_query_uisettings';
|
||||
import { getActiveSeries } from './helpers/get_active_series';
|
||||
|
||||
export async function getSeriesData(req, panel) {
|
||||
const panelIndexPattern = panel.index_pattern;
|
||||
|
@ -29,7 +30,9 @@ export async function getSeriesData(req, panel) {
|
|||
const searchRequest = searchStrategy.getSearchRequest(req, panelIndexPattern);
|
||||
const esQueryConfig = await getEsQueryConfig(req);
|
||||
|
||||
const bodiesPromises = panel.series.map(series => getSeriesRequestParams(req, panel, series, esQueryConfig, capabilities));
|
||||
const bodiesPromises = getActiveSeries(panel)
|
||||
.map(series => getSeriesRequestParams(req, panel, series, esQueryConfig, capabilities));
|
||||
|
||||
const body = (await Promise.all(bodiesPromises))
|
||||
.reduce((acc, items) => acc.concat(items), []);
|
||||
|
||||
|
|
|
@ -16,17 +16,10 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export const getActiveSeries = (panel) => {
|
||||
const shouldNotApplyFilter = ['gauge', 'markdown'].includes(panel.type);
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
return (panel.series || [])
|
||||
.filter(series => !series.hidden || shouldNotApplyFilter);
|
||||
};
|
||||
|
||||
module.directive('stringToNumber', function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function (scope, element, attrs, ngModel) {
|
||||
ngModel.$formatters.push((value) => {
|
||||
return parseFloat(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -22,29 +22,32 @@ import processors from '../response_processors/table';
|
|||
import getLastValue from '../../../../common/get_last_value';
|
||||
import regression from 'regression';
|
||||
import { first, get, set } from 'lodash';
|
||||
import { getActiveSeries } from '../helpers/get_active_series';
|
||||
|
||||
export default function processBucket(panel) {
|
||||
return bucket => {
|
||||
const series = panel.series.map(series => {
|
||||
const timeseries = get(bucket, `${series.id}.timeseries`);
|
||||
const buckets = get(bucket, `${series.id}.buckets`);
|
||||
const series = getActiveSeries(panel)
|
||||
.map(series => {
|
||||
const timeseries = get(bucket, `${series.id}.timeseries`);
|
||||
const buckets = get(bucket, `${series.id}.buckets`);
|
||||
|
||||
if (!timeseries && buckets) {
|
||||
const meta = get(bucket, `${series.id}.meta`);
|
||||
const timeseries = {
|
||||
buckets: get(bucket, `${series.id}.buckets`)
|
||||
};
|
||||
set(bucket, series.id, { meta, timeseries });
|
||||
}
|
||||
if (!timeseries && buckets) {
|
||||
const meta = get(bucket, `${series.id}.meta`);
|
||||
const timeseries = {
|
||||
buckets: get(bucket, `${series.id}.buckets`),
|
||||
};
|
||||
set(bucket, series.id, { meta, timeseries });
|
||||
}
|
||||
|
||||
const processor = buildProcessorFunction(processors, bucket, panel, series);
|
||||
const result = first(processor([]));
|
||||
if (!result) return null;
|
||||
const data = get(result, 'data', []);
|
||||
const linearRegression = regression.linear(data);
|
||||
result.last = getLastValue(data);
|
||||
result.slope = linearRegression.equation[0];
|
||||
return result;
|
||||
});
|
||||
const processor = buildProcessorFunction(processors, bucket, panel, series);
|
||||
const result = first(processor([]));
|
||||
if (!result) return null;
|
||||
const data = get(result, 'data', []);
|
||||
const linearRegression = regression.linear(data);
|
||||
result.last = getLastValue(data);
|
||||
result.slope = linearRegression.equation[0];
|
||||
return result;
|
||||
});
|
||||
return { key: bucket.key, series };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import Boom from 'boom';
|
||||
import Joi from 'joi';
|
||||
import { usage } from '../usage';
|
||||
import { loadData } from './lib/load_data';
|
||||
import { createIndexName } from './lib/create_index_name';
|
||||
import {
|
||||
|
@ -171,6 +172,9 @@ export const createInstallRoute = () => ({
|
|||
.code(403);
|
||||
}
|
||||
|
||||
// track the usage operation in a non-blocking way
|
||||
usage(request).addInstall(params.id);
|
||||
|
||||
return h.response({
|
||||
elasticsearchIndicesCreated: counts,
|
||||
kibanaSavedObjectsLoaded: sampleDataset.savedObjects.length,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import Joi from 'joi';
|
||||
import { usage } from '../usage';
|
||||
import { createIndexName } from './lib/create_index_name';
|
||||
|
||||
export const createUninstallRoute = () => ({
|
||||
|
@ -70,6 +71,9 @@ export const createUninstallRoute = () => ({
|
|||
}
|
||||
}
|
||||
|
||||
// track the usage operation in a non-blocking way
|
||||
usage(request).addUninstall(params.id);
|
||||
|
||||
return {};
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,6 +29,9 @@ import {
|
|||
logsSpecProvider,
|
||||
ecommerceSpecProvider
|
||||
} from './data_sets';
|
||||
import {
|
||||
makeSampleDataUsageCollector
|
||||
} from './usage';
|
||||
|
||||
export function sampleDataMixin(kbnServer, server) {
|
||||
server.route(createListRoute());
|
||||
|
@ -94,4 +97,6 @@ export function sampleDataMixin(kbnServer, server) {
|
|||
server.registerSampleDataset(flightsSpecProvider);
|
||||
server.registerSampleDataset(logsSpecProvider);
|
||||
server.registerSampleDataset(ecommerceSpecProvider);
|
||||
|
||||
makeSampleDataUsageCollector(server);
|
||||
}
|
||||
|
|
|
@ -17,24 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
import * as Hapi from 'hapi';
|
||||
import { fetchProvider } from './collector_fetch';
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.directive('validateLowercase', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, elem, attr, ctrl) {
|
||||
ctrl.$validators.lowercase = function (modelValue, viewValue) {
|
||||
if (ctrl.$isEmpty(modelValue)) {
|
||||
// consider empty models to be valid per lowercase rules
|
||||
return true;
|
||||
}
|
||||
interface KbnServer extends Hapi.Server {
|
||||
usage: any;
|
||||
}
|
||||
|
||||
return viewValue.toLowerCase() === viewValue;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
export function makeSampleDataUsageCollector(server: KbnServer) {
|
||||
let index: string;
|
||||
try {
|
||||
index = server.config().get('kibana.index');
|
||||
} catch (err) {
|
||||
return; // kibana plugin is not enabled (test environment)
|
||||
}
|
||||
|
||||
server.usage.collectorSet.register(
|
||||
server.usage.collectorSet.makeUsageCollector({
|
||||
type: 'sample-data',
|
||||
fetch: fetchProvider(index),
|
||||
})
|
||||
);
|
||||
}
|
180
src/legacy/server/sample_data/usage/collector_fetch.test.ts
Normal file
180
src/legacy/server/sample_data/usage/collector_fetch.test.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { fetchProvider } from './collector_fetch';
|
||||
|
||||
describe('Sample Data Fetch', () => {
|
||||
let callClusterMock: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
callClusterMock = sinon.stub();
|
||||
});
|
||||
|
||||
test('uninitialized .kibana', async () => {
|
||||
const fetch = fetchProvider('index');
|
||||
const telemetry = await fetch(callClusterMock);
|
||||
|
||||
expect(telemetry).toMatchInlineSnapshot(`undefined`);
|
||||
});
|
||||
|
||||
test('installed data set', async () => {
|
||||
const fetch = fetchProvider('index');
|
||||
callClusterMock.returns({
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_id: 'sample-data-telemetry:test1',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:02:09Z',
|
||||
'sample-data-telemetry': { installCount: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const telemetry = await fetch(callClusterMock);
|
||||
|
||||
expect(telemetry).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"installed": Array [
|
||||
"test1",
|
||||
],
|
||||
"last_install_date": "2019-03-13T22:02:09.000Z",
|
||||
"last_install_set": "test1",
|
||||
"last_uninstall_date": null,
|
||||
"last_uninstall_set": null,
|
||||
"uninstalled": Array [],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('multiple installed data sets', async () => {
|
||||
const fetch = fetchProvider('index');
|
||||
callClusterMock.returns({
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_id: 'sample-data-telemetry:test1',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:02:09Z',
|
||||
'sample-data-telemetry': { installCount: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 'sample-data-telemetry:test2',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:13:17Z',
|
||||
'sample-data-telemetry': { installCount: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const telemetry = await fetch(callClusterMock);
|
||||
|
||||
expect(telemetry).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"installed": Array [
|
||||
"test1",
|
||||
"test2",
|
||||
],
|
||||
"last_install_date": "2019-03-13T22:13:17.000Z",
|
||||
"last_install_set": "test2",
|
||||
"last_uninstall_date": null,
|
||||
"last_uninstall_set": null,
|
||||
"uninstalled": Array [],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('installed data set, missing counts', async () => {
|
||||
const fetch = fetchProvider('index');
|
||||
callClusterMock.returns({
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_id: 'sample-data-telemetry:test1',
|
||||
_source: { updated_at: '2019-03-13T22:02:09Z', 'sample-data-telemetry': {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const telemetry = await fetch(callClusterMock);
|
||||
|
||||
expect(telemetry).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"installed": Array [],
|
||||
"last_install_date": null,
|
||||
"last_install_set": null,
|
||||
"last_uninstall_date": null,
|
||||
"last_uninstall_set": null,
|
||||
"uninstalled": Array [],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('installed and uninstalled data sets', async () => {
|
||||
const fetch = fetchProvider('index');
|
||||
callClusterMock.returns({
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_id: 'sample-data-telemetry:test0',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:29:32Z',
|
||||
'sample-data-telemetry': { installCount: 4, unInstallCount: 4 },
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 'sample-data-telemetry:test1',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:02:09Z',
|
||||
'sample-data-telemetry': { installCount: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: 'sample-data-telemetry:test2',
|
||||
_source: {
|
||||
updated_at: '2019-03-13T22:13:17Z',
|
||||
'sample-data-telemetry': { installCount: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const telemetry = await fetch(callClusterMock);
|
||||
|
||||
expect(telemetry).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"installed": Array [
|
||||
"test1",
|
||||
"test2",
|
||||
],
|
||||
"last_install_date": "2019-03-13T22:13:17.000Z",
|
||||
"last_install_set": "test2",
|
||||
"last_uninstall_date": "2019-03-13T22:29:32.000Z",
|
||||
"last_uninstall_set": "test0",
|
||||
"uninstalled": Array [
|
||||
"test0",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
132
src/legacy/server/sample_data/usage/collector_fetch.ts
Normal file
132
src/legacy/server/sample_data/usage/collector_fetch.ts
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
interface SearchHit {
|
||||
_id: string;
|
||||
_source: {
|
||||
'sample-data-telemetry': {
|
||||
installCount?: number;
|
||||
unInstallCount?: number;
|
||||
};
|
||||
updated_at: Date;
|
||||
};
|
||||
}
|
||||
|
||||
interface TelemetryResponse {
|
||||
installed: string[];
|
||||
uninstalled: string[];
|
||||
last_install_date: moment.Moment | null;
|
||||
last_install_set: string | null;
|
||||
last_uninstall_date: moment.Moment | null;
|
||||
last_uninstall_set: string | null;
|
||||
}
|
||||
|
||||
export function fetchProvider(index: string) {
|
||||
return async (callCluster: any) => {
|
||||
const response = await callCluster('search', {
|
||||
index,
|
||||
body: {
|
||||
query: { term: { type: { value: 'sample-data-telemetry' } } },
|
||||
_source: { includes: ['sample-data-telemetry', 'type', 'updated_at'] },
|
||||
},
|
||||
filter_path: 'hits.hits._id,hits.hits._source',
|
||||
ignore: [404],
|
||||
});
|
||||
|
||||
const getLast = (
|
||||
dataSet: string,
|
||||
dataDate: moment.Moment,
|
||||
accumSet: string | null,
|
||||
accumDate: moment.Moment | null
|
||||
) => {
|
||||
let lastDate = accumDate;
|
||||
let lastSet = accumSet;
|
||||
|
||||
if (!accumDate || (accumDate && dataDate > accumDate)) {
|
||||
// if a max date has not been accumulated yet, or if the current date is the new max
|
||||
lastDate = dataDate;
|
||||
lastSet = dataSet;
|
||||
}
|
||||
|
||||
return { lastDate, lastSet };
|
||||
};
|
||||
|
||||
const initial: TelemetryResponse = {
|
||||
installed: [],
|
||||
uninstalled: [],
|
||||
last_install_date: null,
|
||||
last_install_set: null,
|
||||
last_uninstall_date: null,
|
||||
last_uninstall_set: null,
|
||||
};
|
||||
|
||||
const hits: any[] = get(response, 'hits.hits', []);
|
||||
if (hits == null || hits.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return hits.reduce((telemetry: TelemetryResponse, hit: SearchHit) => {
|
||||
const { installCount = 0, unInstallCount = 0 } = hit._source['sample-data-telemetry'] || {
|
||||
installCount: 0,
|
||||
unInstallCount: 0,
|
||||
};
|
||||
|
||||
if (installCount === 0 && unInstallCount === 0) {
|
||||
return telemetry;
|
||||
}
|
||||
|
||||
const isSampleDataSetInstalled = installCount - unInstallCount > 0;
|
||||
const dataSet = hit._id.replace('sample-data-telemetry:', ''); // sample-data-telemetry:ecommerce => ecommerce
|
||||
const dataDate = moment.utc(hit._source.updated_at);
|
||||
|
||||
if (isSampleDataSetInstalled) {
|
||||
const { lastDate, lastSet } = getLast(
|
||||
dataSet,
|
||||
dataDate,
|
||||
telemetry.last_install_set,
|
||||
telemetry.last_install_date
|
||||
);
|
||||
|
||||
return {
|
||||
...telemetry,
|
||||
installed: telemetry.installed.concat(dataSet),
|
||||
last_install_date: lastDate,
|
||||
last_install_set: lastSet,
|
||||
};
|
||||
} else {
|
||||
const { lastDate, lastSet } = getLast(
|
||||
dataSet,
|
||||
dataDate,
|
||||
telemetry.last_uninstall_set,
|
||||
telemetry.last_uninstall_date
|
||||
);
|
||||
|
||||
return {
|
||||
...telemetry,
|
||||
uninstalled: telemetry.uninstalled.concat(dataSet),
|
||||
last_uninstall_date: lastDate,
|
||||
last_uninstall_set: lastSet,
|
||||
};
|
||||
}
|
||||
}, initial);
|
||||
};
|
||||
}
|
21
src/legacy/server/sample_data/usage/index.ts
Normal file
21
src/legacy/server/sample_data/usage/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { makeSampleDataUsageCollector } from './collector';
|
||||
export { usage } from './usage';
|
59
src/legacy/server/sample_data/usage/usage.ts
Normal file
59
src/legacy/server/sample_data/usage/usage.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as Hapi from 'hapi';
|
||||
|
||||
const SAVED_OBJECT_ID = 'sample-data-telemetry';
|
||||
|
||||
export function usage(request: Hapi.Request) {
|
||||
const { server } = request;
|
||||
|
||||
const handleIncrementError = (err: Error) => {
|
||||
if (err != null) {
|
||||
server.log(['debug', 'sample_data', 'telemetry'], err.stack);
|
||||
}
|
||||
server.log(
|
||||
['warning', 'sample_data', 'telemetry'],
|
||||
`saved objects repository incrementCounter encountered an error: ${err}`
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
savedObjects: { getSavedObjectsRepository },
|
||||
} = server;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
||||
return {
|
||||
addInstall: async (dataSet: string) => {
|
||||
try {
|
||||
internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`);
|
||||
} catch (err) {
|
||||
handleIncrementError(err);
|
||||
}
|
||||
},
|
||||
addUninstall: async (dataSet: string) => {
|
||||
try {
|
||||
internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `unInstallCount`);
|
||||
} catch (err) {
|
||||
handleIncrementError(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -17,15 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
interface AggParam {
|
||||
type: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
module.directive('uiSelectFocusOn', ($timeout) => ({
|
||||
restrict: 'A',
|
||||
require: 'uiSelect',
|
||||
link: function (scope, elem, attrs, uiSelect) {
|
||||
scope.$on(attrs.uiSelectFocusOn, () => {
|
||||
$timeout(() => uiSelect.activate());
|
||||
});
|
||||
}
|
||||
}));
|
||||
export { AggParam };
|
|
@ -20,6 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
import { AggParams } from './agg_params';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
/**
|
||||
* Generic AggType Constructor
|
||||
|
@ -118,6 +119,7 @@ function AggType(config) {
|
|||
if (config.customLabels !== false) {
|
||||
this.params.push({
|
||||
name: 'customLabel',
|
||||
displayName: i18n.translate('common.ui.aggTypes.string.customLabel', { defaultMessage: 'Custom label' }),
|
||||
type: 'string',
|
||||
write: _.noop
|
||||
});
|
||||
|
|
|
@ -311,6 +311,7 @@ export const termsBucketAgg = new BucketAggType({
|
|||
},
|
||||
{
|
||||
name: 'exclude',
|
||||
displayName: i18n.translate('common.ui.aggTypes.buckets.terms.excludeLabel', { defaultMessage: 'Exclude' }),
|
||||
type: 'string',
|
||||
advanced: true,
|
||||
disabled: isNotType('string'),
|
||||
|
@ -318,6 +319,7 @@ export const termsBucketAgg = new BucketAggType({
|
|||
},
|
||||
{
|
||||
name: 'include',
|
||||
displayName: i18n.translate('common.ui.aggTypes.buckets.terms.includeLabel', { defaultMessage: 'Include' }),
|
||||
type: 'string',
|
||||
advanced: true,
|
||||
disabled: isNotType('string'),
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<div class="form-group">
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<label for="visEditorStringInput{{agg.id}}{{aggParam.name}}">{{ aggParam.name | label }}</label>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
id="visEditorStringInput{{agg.id}}{{aggParam.name}}"
|
||||
ng-model="agg.params[aggParam.name]"
|
||||
class="form-control"
|
||||
>
|
||||
</div>
|
||||
</div>
|
42
src/legacy/ui/public/agg_types/controls/string.tsx
Normal file
42
src/legacy/ui/public/agg_types/controls/string.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { AggParamEditorProps } from '../../vis/editors/default';
|
||||
|
||||
function StringParamEditor({ agg, aggParam, value, setValue }: AggParamEditorProps<string>) {
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={aggParam.displayName || aggParam.name}
|
||||
className="form-group"
|
||||
fullWidth={true}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={value || ''}
|
||||
data-test-subj={`visEditorStringInput${agg.id}${aggParam.name}`}
|
||||
onChange={ev => setValue(ev.target.value)}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
export { StringParamEditor };
|
1
src/legacy/ui/public/agg_types/index.d.ts
vendored
1
src/legacy/ui/public/agg_types/index.d.ts
vendored
|
@ -17,4 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { AggParam } from './agg_param';
|
||||
export { AggType } from './agg_type';
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import editorHtml from '../controls/string.html';
|
||||
import { BaseParamType } from './base';
|
||||
import { createLegacyClass } from '../../utils/legacy_class';
|
||||
import { StringParamEditor } from '../controls/string';
|
||||
import { BaseParamType } from './base';
|
||||
|
||||
createLegacyClass(StringParamType).inherits(BaseParamType);
|
||||
function StringParamType(config) {
|
||||
StringParamType.Super.call(this, config);
|
||||
}
|
||||
|
||||
StringParamType.prototype.editor = editorHtml;
|
||||
StringParamType.prototype.editorComponent = StringParamEditor;
|
||||
|
||||
/**
|
||||
* Write the aggregation parameter.
|
||||
|
@ -45,3 +45,4 @@ StringParamType.prototype.write = function (aggConfig, output) {
|
|||
};
|
||||
|
||||
export { StringParamType };
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a href class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading"><span ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-collapse" collapse="!isOpen">
|
||||
<div class="panel-body" ng-transclude></div>
|
||||
</div>
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<div class="panel-group" ng-transclude></div>
|
|
@ -1,130 +0,0 @@
|
|||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
|
||||
.constant('accordionConfig', {
|
||||
closeOthers: true
|
||||
})
|
||||
|
||||
.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
|
||||
|
||||
// This array keeps track of the accordion groups
|
||||
this.groups = [];
|
||||
|
||||
// Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
|
||||
this.closeOthers = function(openGroup) {
|
||||
var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
|
||||
if ( closeOthers ) {
|
||||
angular.forEach(this.groups, function (group) {
|
||||
if ( group !== openGroup ) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// This is called from the accordion-group directive to add itself to the accordion
|
||||
this.addGroup = function(groupScope) {
|
||||
var that = this;
|
||||
this.groups.push(groupScope);
|
||||
|
||||
groupScope.$on('$destroy', function (event) {
|
||||
that.removeGroup(groupScope);
|
||||
});
|
||||
};
|
||||
|
||||
// This is called from the accordion-group directive when to remove itself
|
||||
this.removeGroup = function(group) {
|
||||
var index = this.groups.indexOf(group);
|
||||
if ( index !== -1 ) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
}])
|
||||
|
||||
// The accordion directive simply sets up the directive controller
|
||||
// and adds an accordion CSS class to itself element.
|
||||
.directive('accordion', function () {
|
||||
return {
|
||||
restrict:'EA',
|
||||
controller:'AccordionController',
|
||||
transclude: true,
|
||||
replace: false,
|
||||
templateUrl: 'template/accordion/accordion.html'
|
||||
};
|
||||
})
|
||||
|
||||
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
|
||||
.directive('accordionGroup', function() {
|
||||
return {
|
||||
require:'^accordion', // We need this directive to be inside an accordion
|
||||
restrict:'EA',
|
||||
transclude:true, // It transcludes the contents of the directive into the template
|
||||
replace: true, // The element containing the directive will be replaced with the template
|
||||
templateUrl:'template/accordion/accordion-group.html',
|
||||
scope: {
|
||||
heading: '@', // Interpolate the heading attribute onto this scope
|
||||
isOpen: '=?',
|
||||
isDisabled: '=?'
|
||||
},
|
||||
controller: function() {
|
||||
this.setHeading = function(element) {
|
||||
this.heading = element;
|
||||
};
|
||||
},
|
||||
link: function(scope, element, attrs, accordionCtrl) {
|
||||
accordionCtrl.addGroup(scope);
|
||||
|
||||
scope.$watch('isOpen', function(value) {
|
||||
if ( value ) {
|
||||
accordionCtrl.closeOthers(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.toggleOpen = function() {
|
||||
if ( !scope.isDisabled ) {
|
||||
scope.isOpen = !scope.isOpen;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// Use accordion-heading below an accordion-group to provide a heading containing HTML
|
||||
// <accordion-group>
|
||||
// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
|
||||
// </accordion-group>
|
||||
.directive('accordionHeading', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: true, // Grab the contents to be used as the heading
|
||||
template: '', // In effect remove this element!
|
||||
replace: true,
|
||||
require: '^accordionGroup',
|
||||
link: function(scope, element, attr, accordionGroupCtrl, transclude) {
|
||||
// Pass the heading to the accordion-group controller
|
||||
// so that it can be transcluded into the right place in the template
|
||||
// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
|
||||
accordionGroupCtrl.setHeading(transclude(scope, function() {}));
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// Use in the accordion-group template to indicate where you want the heading to be transcluded
|
||||
// You must provide the property on the accordion-group controller that will hold the transcluded element
|
||||
// <div class="accordion-group">
|
||||
// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
|
||||
// ...
|
||||
// </div>
|
||||
.directive('accordionTransclude', function() {
|
||||
return {
|
||||
require: '^accordionGroup',
|
||||
link: function(scope, element, attr, controller) {
|
||||
scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
|
||||
if ( heading ) {
|
||||
element.html('');
|
||||
element.append(heading);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -18,14 +18,3 @@ angular.module('ui.bootstrap.alert', [])
|
|||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('dismissOnTimeout', ['$timeout', function($timeout) {
|
||||
return {
|
||||
require: 'alert',
|
||||
link: function(scope, element, attrs, alertCtrl) {
|
||||
$timeout(function(){
|
||||
alertCtrl.close();
|
||||
}, parseInt(attrs.dismissOnTimeout, 10));
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -35,40 +35,4 @@ angular.module('ui.bootstrap.buttons', [])
|
|||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('btnCheckbox', function () {
|
||||
return {
|
||||
require: ['btnCheckbox', 'ngModel'],
|
||||
controller: 'ButtonsController',
|
||||
link: function (scope, element, attrs, ctrls) {
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
function getTrueValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxTrue, true);
|
||||
}
|
||||
|
||||
function getFalseValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxFalse, false);
|
||||
}
|
||||
|
||||
function getCheckboxValue(attributeValue, defaultValue) {
|
||||
var val = scope.$eval(attributeValue);
|
||||
return angular.isDefined(val) ? val : defaultValue;
|
||||
}
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function () {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.bind(buttonsCtrl.toggleEvent, function () {
|
||||
scope.$apply(function () {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
|
||||
|
||||
.directive('collapse', ['$transition', function ($transition) {
|
||||
|
||||
return {
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
var initialAnimSkip = true;
|
||||
var currentTransition;
|
||||
|
||||
function doTransition(change) {
|
||||
var newTransition = $transition(element, change);
|
||||
if (currentTransition) {
|
||||
currentTransition.cancel();
|
||||
}
|
||||
currentTransition = newTransition;
|
||||
newTransition.then(newTransitionDone, newTransitionDone);
|
||||
return newTransition;
|
||||
|
||||
function newTransitionDone() {
|
||||
// Make sure it's this transition, otherwise, leave it alone.
|
||||
if (currentTransition === newTransition) {
|
||||
currentTransition = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expand() {
|
||||
if (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
expandDone();
|
||||
} else {
|
||||
element.removeClass('collapse').addClass('collapsing');
|
||||
doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
|
||||
}
|
||||
}
|
||||
|
||||
function expandDone() {
|
||||
element.removeClass('collapsing');
|
||||
element.addClass('collapse in');
|
||||
element.css({height: 'auto'});
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
if (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
collapseDone();
|
||||
element.css({height: 0});
|
||||
} else {
|
||||
// CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
|
||||
element.css({ height: element[0].scrollHeight + 'px' });
|
||||
//trigger reflow so a browser realizes that height was updated from auto to a specific value
|
||||
var x = element[0].offsetWidth;
|
||||
|
||||
element.removeClass('collapse in').addClass('collapsing');
|
||||
|
||||
doTransition({ height: 0 }).then(collapseDone);
|
||||
}
|
||||
}
|
||||
|
||||
function collapseDone() {
|
||||
element.removeClass('collapsing');
|
||||
element.addClass('collapse');
|
||||
}
|
||||
|
||||
scope.$watch(attrs.collapse, function (shouldCollapse) {
|
||||
if (shouldCollapse) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
|
@ -1,126 +0,0 @@
|
|||
angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) {
|
||||
|
||||
this.parsers = {};
|
||||
|
||||
var formatCodeToRegex = {
|
||||
'yyyy': {
|
||||
regex: '\\d{4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
},
|
||||
'yy': {
|
||||
regex: '\\d{2}',
|
||||
apply: function(value) { this.year = +value + 2000; }
|
||||
},
|
||||
'y': {
|
||||
regex: '\\d{1,4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
},
|
||||
'MMMM': {
|
||||
regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
|
||||
},
|
||||
'MMM': {
|
||||
regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
|
||||
},
|
||||
'MM': {
|
||||
regex: '0[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
},
|
||||
'M': {
|
||||
regex: '[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
},
|
||||
'dd': {
|
||||
regex: '[0-2][0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
},
|
||||
'd': {
|
||||
regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
},
|
||||
'EEEE': {
|
||||
regex: $locale.DATETIME_FORMATS.DAY.join('|')
|
||||
},
|
||||
'EEE': {
|
||||
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
|
||||
}
|
||||
};
|
||||
|
||||
function createParser(format) {
|
||||
var map = [], regex = format.split('');
|
||||
|
||||
angular.forEach(formatCodeToRegex, function(data, code) {
|
||||
var index = format.indexOf(code);
|
||||
|
||||
if (index > -1) {
|
||||
format = format.split('');
|
||||
|
||||
regex[index] = '(' + data.regex + ')';
|
||||
format[index] = '$'; // Custom symbol to define consumed part of format
|
||||
for (var i = index + 1, n = index + code.length; i < n; i++) {
|
||||
regex[i] = '';
|
||||
format[i] = '$';
|
||||
}
|
||||
format = format.join('');
|
||||
|
||||
map.push({ index: index, apply: data.apply });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
regex: new RegExp('^' + regex.join('') + '$'),
|
||||
map: orderByFilter(map, 'index')
|
||||
};
|
||||
}
|
||||
|
||||
this.parse = function(input, format) {
|
||||
if ( !angular.isString(input) || !format ) {
|
||||
return input;
|
||||
}
|
||||
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
|
||||
if ( !this.parsers[format] ) {
|
||||
this.parsers[format] = createParser(format);
|
||||
}
|
||||
|
||||
var parser = this.parsers[format],
|
||||
regex = parser.regex,
|
||||
map = parser.map,
|
||||
results = input.match(regex);
|
||||
|
||||
if ( results && results.length ) {
|
||||
var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;
|
||||
|
||||
for( var i = 1, n = results.length; i < n; i++ ) {
|
||||
var mapper = map[i-1];
|
||||
if ( mapper.apply ) {
|
||||
mapper.apply.call(fields, results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( isValid(fields.year, fields.month, fields.date) ) {
|
||||
dt = new Date( fields.year, fields.month, fields.date, fields.hours);
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if date is valid for specific month (and year for February).
|
||||
// Month: 0 = Jan, 1 = Feb, etc
|
||||
function isValid(year, month, date) {
|
||||
if ( month === 1 && date > 28) {
|
||||
return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
|
||||
}
|
||||
|
||||
if ( month === 3 || month === 5 || month === 8 || month === 10) {
|
||||
return date < 31;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}]);
|
14
src/legacy/ui/public/angular-bootstrap/index.js
vendored
14
src/legacy/ui/public/angular-bootstrap/index.js
vendored
|
@ -23,11 +23,9 @@ uiModules.get('kibana', [
|
|||
angular.module('ui.bootstrap', [
|
||||
'ui.bootstrap.tpls',
|
||||
'ui.bootstrap.transition',
|
||||
'ui.bootstrap.collapse',
|
||||
'ui.bootstrap.alert',
|
||||
'ui.bootstrap.bindHtml',
|
||||
'ui.bootstrap.buttons',
|
||||
'ui.bootstrap.dateparser',
|
||||
'ui.bootstrap.position',
|
||||
'ui.bootstrap.dropdown',
|
||||
'ui.bootstrap.modal',
|
||||
|
@ -35,7 +33,6 @@ angular.module('ui.bootstrap', [
|
|||
'ui.bootstrap.tooltip',
|
||||
'ui.bootstrap.popover',
|
||||
'ui.bootstrap.progressbar',
|
||||
'ui.bootstrap.rating',
|
||||
'ui.bootstrap.tabs',
|
||||
'ui.bootstrap.timepicker',
|
||||
'ui.bootstrap.typeahead'
|
||||
|
@ -53,7 +50,6 @@ angular.module('ui.bootstrap.tpls', [
|
|||
'template/progressbar/bar.html',
|
||||
'template/progressbar/progress.html',
|
||||
'template/progressbar/progressbar.html',
|
||||
'template/rating/rating.html',
|
||||
'template/tabs/tab.html',
|
||||
'template/tabs/tabset.html',
|
||||
'template/timepicker/timepicker.html',
|
||||
|
@ -61,19 +57,15 @@ angular.module('ui.bootstrap.tpls', [
|
|||
'template/typeahead/typeahead-popup.html'
|
||||
]);
|
||||
|
||||
import './accordion/accordion';
|
||||
import './alert/alert';
|
||||
import './bindHtml/bindHtml';
|
||||
import './buttons/buttons';
|
||||
import './collapse/collapse';
|
||||
import './dateparser/dateparser';
|
||||
import './dropdown/dropdown';
|
||||
import './modal/modal';
|
||||
import './pagination/pagination';
|
||||
import './popover/popover';
|
||||
import './position/position';
|
||||
import './progressbar/progressbar';
|
||||
import './rating/rating';
|
||||
import './tabs/tabs';
|
||||
import './timepicker/timepicker';
|
||||
import './tooltip/tooltip';
|
||||
|
@ -146,12 +138,6 @@ angular.module('template/progressbar/progressbar.html', []).run(['$templateCache
|
|||
$templateCache.put('template/progressbar/progressbar.html', progressbar);
|
||||
}]);
|
||||
|
||||
import rating from './rating/rating.html';
|
||||
|
||||
angular.module('template/rating/rating.html', []).run(['$templateCache', function($templateCache) {
|
||||
$templateCache.put('template/rating/rating.html', rating);
|
||||
}]);
|
||||
|
||||
import tab from './tabs/tab.html';
|
||||
|
||||
angular.module('template/tabs/tab.html', []).run(['$templateCache', function($templateCache) {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<span ng-mouseleave="reset()" ng-keydown="onKeydown($event)" tabindex="0" role="slider" aria-valuemin="0" aria-valuemax="{{range.length}}" aria-valuenow="{{value}}">
|
||||
<i ng-repeat="r in range track by $index" ng-mouseenter="enter($index + 1)" ng-click="rate($index + 1)" class="fa" ng-class="$index < value && (r.stateOn || 'fa-star') || (r.stateOff || 'fa-star-o')">
|
||||
<span class="euiScreenReaderOnly">({{ $index < value ? '*' : ' ' }})</span>
|
||||
</i>
|
||||
</span>
|
|
@ -1,83 +0,0 @@
|
|||
angular.module('ui.bootstrap.rating', [])
|
||||
|
||||
.constant('ratingConfig', {
|
||||
max: 5,
|
||||
stateOn: null,
|
||||
stateOff: null
|
||||
})
|
||||
|
||||
.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) {
|
||||
var ngModelCtrl = { $setViewValue: angular.noop };
|
||||
|
||||
this.init = function(ngModelCtrl_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
ngModelCtrl.$render = this.render;
|
||||
|
||||
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
|
||||
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
|
||||
|
||||
var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) :
|
||||
new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max );
|
||||
$scope.range = this.buildTemplateObjects(ratingStates);
|
||||
};
|
||||
|
||||
this.buildTemplateObjects = function(states) {
|
||||
for (var i = 0, n = states.length; i < n; i++) {
|
||||
states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]);
|
||||
}
|
||||
return states;
|
||||
};
|
||||
|
||||
$scope.rate = function(value) {
|
||||
if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) {
|
||||
ngModelCtrl.$setViewValue(value);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.enter = function(value) {
|
||||
if ( !$scope.readonly ) {
|
||||
$scope.value = value;
|
||||
}
|
||||
$scope.onHover({value: value});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.value = ngModelCtrl.$viewValue;
|
||||
$scope.onLeave();
|
||||
};
|
||||
|
||||
$scope.onKeydown = function(evt) {
|
||||
if (/(37|38|39|40)/.test(evt.which)) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
$scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) );
|
||||
}
|
||||
};
|
||||
|
||||
this.render = function() {
|
||||
$scope.value = ngModelCtrl.$viewValue;
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('rating', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
require: ['rating', 'ngModel'],
|
||||
scope: {
|
||||
readonly: '=?',
|
||||
onHover: '&',
|
||||
onLeave: '&'
|
||||
},
|
||||
controller: 'RatingController',
|
||||
templateUrl: 'template/rating/rating.html',
|
||||
replace: true,
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
if ( ngModelCtrl ) {
|
||||
ratingCtrl.init( ngModelCtrl );
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -26,7 +26,6 @@ import '../config';
|
|||
import '../courier';
|
||||
import '../debounce';
|
||||
import '../doc_title';
|
||||
import '../elastic_textarea';
|
||||
import '../es';
|
||||
import '../events';
|
||||
import '../fancy_forms';
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { basePathServiceMock } from '../../../../../core/public/mocks';
|
||||
import { __newPlatformInit__, initChromeBasePathApi } from './base_path';
|
||||
|
||||
function initChrome() {
|
||||
|
@ -25,11 +26,7 @@ function initChrome() {
|
|||
return chrome;
|
||||
}
|
||||
|
||||
const newPlatformBasePath = {
|
||||
get: jest.fn().mockReturnValue('get'),
|
||||
addToPath: jest.fn().mockReturnValue('addToPath'),
|
||||
removeFromPath: jest.fn().mockReturnValue('removeFromPath'),
|
||||
};
|
||||
const newPlatformBasePath = basePathServiceMock.createStartContract();
|
||||
__newPlatformInit__(newPlatformBasePath);
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -19,14 +19,12 @@
|
|||
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
import { chromeServiceMock } from '../../../../../core/public/mocks';
|
||||
import { __newPlatformInit__, initChromeControlsApi } from './controls';
|
||||
|
||||
const newPlatformChrome = {
|
||||
setIsVisible: jest.fn(),
|
||||
getIsVisible$: jest.fn(),
|
||||
};
|
||||
const newPlatformChrome = chromeServiceMock.createStartContract();
|
||||
|
||||
__newPlatformInit__(newPlatformChrome as any);
|
||||
__newPlatformInit__(newPlatformChrome);
|
||||
|
||||
function setup() {
|
||||
const isVisible$ = new Rx.BehaviorSubject(true);
|
||||
|
|
|
@ -19,17 +19,12 @@
|
|||
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
import { chromeServiceMock } from '../../../../../core/public/mocks';
|
||||
import { __newPlatformInit__, initChromeThemeApi } from './theme';
|
||||
|
||||
const newPlatformChrome = {
|
||||
setBrand: jest.fn(),
|
||||
getBrand$: jest.fn(),
|
||||
addApplicationClass: jest.fn(),
|
||||
removeApplicationClass: jest.fn(),
|
||||
getApplicationClasses$: jest.fn(),
|
||||
};
|
||||
const newPlatformChrome = chromeServiceMock.createStartContract();
|
||||
|
||||
__newPlatformInit__(newPlatformChrome as any);
|
||||
__newPlatformInit__(newPlatformChrome);
|
||||
|
||||
function setup() {
|
||||
const brand$ = new Rx.BehaviorSubject({ logo: 'foo', smallLogo: 'foo' });
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import $ from 'jquery';
|
||||
import '../confirm_click';
|
||||
import 'plugins/kibana/discover/index';
|
||||
import sinon from 'sinon';
|
||||
|
||||
let $window;
|
||||
|
||||
let $parentScope;
|
||||
|
||||
let $scope;
|
||||
|
||||
let $elem;
|
||||
|
||||
const init = function (confirm) {
|
||||
// Load the application
|
||||
ngMock.module('kibana', function ($provide) {
|
||||
$window = {
|
||||
confirm: sinon.stub().returns(confirm)
|
||||
};
|
||||
$provide.value('$window', $window);
|
||||
});
|
||||
|
||||
// Create the scope
|
||||
ngMock.inject(function ($rootScope, $compile) {
|
||||
// Give us a scope
|
||||
$parentScope = $rootScope;
|
||||
|
||||
// Create the element
|
||||
$elem = angular.element(
|
||||
'<a confirm-click="runThis()">runThis</a>'
|
||||
);
|
||||
|
||||
// And compile it
|
||||
$compile($elem)($parentScope);
|
||||
|
||||
// Fire a digest cycle
|
||||
$elem.scope().$digest();
|
||||
|
||||
// Grab the isolate scope so we can test it
|
||||
$scope = $elem.scope();
|
||||
|
||||
// Add a function to check the run status of.
|
||||
$scope.runThis = sinon.spy();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
describe('confirmClick directive', function () {
|
||||
|
||||
|
||||
describe('event handlers', function () {
|
||||
let events;
|
||||
|
||||
beforeEach(function () {
|
||||
init();
|
||||
events = $._data($elem[0], 'events');
|
||||
});
|
||||
|
||||
it('should get a click handler', function () {
|
||||
expect(events).to.be.a(Object);
|
||||
expect(events.click).to.be.a(Array);
|
||||
});
|
||||
|
||||
it('should unbind click handlers when the scope is destroyed', function () {
|
||||
$scope.$destroy();
|
||||
expect(events.click).to.be(undefined);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('confirmed', function () {
|
||||
beforeEach(() => init(true));
|
||||
|
||||
it('should trigger window.confirm when clicked', function () {
|
||||
$elem.click();
|
||||
expect($window.confirm.called).to.be(true);
|
||||
});
|
||||
|
||||
it('should run the click function when positively confirmed', function () {
|
||||
$elem.click();
|
||||
expect($scope.runThis.called).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('not confirmed', function () {
|
||||
beforeEach(() => init(false));
|
||||
|
||||
it('should not run the click function when canceled', function () {
|
||||
$elem.click();
|
||||
expect($scope.runThis.called).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import '../validate_index_pattern';
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
|
||||
describe('Validate index pattern directive', function () {
|
||||
let $compile;
|
||||
let $rootScope;
|
||||
const noWildcardHtml = '<input type="text" ng-model="indexName" validate-index-pattern />';
|
||||
const requiredHtml = '<input type="text" ng-model="indexName" validate-index-pattern required />';
|
||||
const allowWildcardHtml = '<input type="text" ng-model="indexName" validate-index-pattern validate-index-pattern-allow-wildcard />';
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
beforeEach(ngMock.inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
function checkPattern(input, html) {
|
||||
$rootScope.indexName = input;
|
||||
const element = $compile(html)($rootScope);
|
||||
$rootScope.$digest();
|
||||
return element;
|
||||
}
|
||||
|
||||
const emptyPatterns = [
|
||||
undefined,
|
||||
null,
|
||||
''
|
||||
];
|
||||
|
||||
const badPatterns = [
|
||||
'.',
|
||||
'..',
|
||||
'foo\\bar',
|
||||
'foo/bar',
|
||||
'foo?bar',
|
||||
'foo"bar',
|
||||
'foo<bar',
|
||||
'foo>bar',
|
||||
'foo|bar',
|
||||
'foo bar',
|
||||
];
|
||||
|
||||
const goodPatterns = [
|
||||
'...',
|
||||
'foo',
|
||||
'foo.bar',
|
||||
'[foo-]YYYY-MM-DD',
|
||||
'foo:bar',
|
||||
'foo,bar',
|
||||
];
|
||||
|
||||
const wildcardPatterns = [
|
||||
'foo*',
|
||||
'foo.bar*',
|
||||
'foo.*'
|
||||
];
|
||||
|
||||
badPatterns.forEach(function (pattern) {
|
||||
it('should not accept index pattern: ' + pattern, function () {
|
||||
const element = checkPattern(pattern, noWildcardHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.not.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
goodPatterns.forEach(function (pattern) {
|
||||
it('should accept index pattern: ' + pattern, function () {
|
||||
const element = checkPattern(pattern, noWildcardHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.not.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
emptyPatterns.forEach(function (pattern) {
|
||||
it('should not accept index pattern: ' + pattern, function () {
|
||||
const element = checkPattern(pattern, requiredHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.not.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should disallow wildcards by default', function () {
|
||||
wildcardPatterns.forEach(function (pattern) {
|
||||
const element = checkPattern(pattern, noWildcardHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.not.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow wildcards if the allow-wildcard attribute is present', function () {
|
||||
wildcardPatterns.forEach(function (pattern) {
|
||||
const element = checkPattern(pattern, allowWildcardHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.not.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.directive('confirmClick', function ($window, i18n) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, $elem, attrs) {
|
||||
$elem.bind('click', function () {
|
||||
const message = attrs.confirmation || i18n('common.ui.directives.confirmClickButtonLabel', {
|
||||
defaultMessage: 'Are you sure?'
|
||||
});
|
||||
if ($window.confirm(message)) { // eslint-disable-line no-alert
|
||||
const action = attrs.confirmClick;
|
||||
if (action) {
|
||||
$scope.$apply($scope.$eval(action));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$elem.unbind('click');
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
const html = '<span class="dropzone" ng-transclude></span>';
|
||||
|
||||
module.directive('fileUpload', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {
|
||||
onRead: '&',
|
||||
onLocate: '&',
|
||||
uploadSelector: '@'
|
||||
},
|
||||
template: html,
|
||||
link: function ($scope, $elem, attrs) {
|
||||
const $button = $elem.find($scope.uploadSelector);
|
||||
const $dropzone = $elem.find('.dropzone');
|
||||
|
||||
const handleFile = (file) => {
|
||||
if (_.isUndefined(file)) return;
|
||||
|
||||
if (_.has(attrs, 'onRead')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
$scope.$apply(function () {
|
||||
$scope.onRead({ fileContents: e.target.result });
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
if (_.has(attrs, 'onLocate')) {
|
||||
$scope.$apply(function () {
|
||||
$scope.onLocate({ file });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$dropzone.on('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
|
||||
$dropzone.on('dragenter', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
);
|
||||
|
||||
$dropzone.on('drop', function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const file = _.get(e, 'originalEvent.dataTransfer.files[0]');
|
||||
|
||||
if (file) {
|
||||
handleFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
if ($button) {
|
||||
const $fileInput = $('<input type="file" style="opacity: 0;' +
|
||||
' display:none; position:absolute; right: -999999999px" id="testfile" />');
|
||||
$elem.append($fileInput);
|
||||
|
||||
$fileInput.on('change', function (e) {
|
||||
const target = e.srcElement || e.target;
|
||||
if (_.get(target, 'files.length')) {
|
||||
handleFile(target.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$button.on('click', function () {
|
||||
$fileInput.val(null);
|
||||
$fileInput.trigger('click');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
import * as Rx from 'rxjs';
|
||||
import { map, switchMap, share } from 'rxjs/operators';
|
||||
|
||||
const multipleUsageErrorMessage = 'Cannot use input-base-sixty-four directive on input with `multiple` attribute';
|
||||
|
||||
const createFileContent$ = (file) => {
|
||||
return Rx.Observable.create(observer => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = (err) => {
|
||||
observer.error(err);
|
||||
};
|
||||
|
||||
reader.onload = () => {
|
||||
observer.next(reader.result);
|
||||
observer.complete();
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
return () => {
|
||||
reader.abort();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
module.directive('inputBaseSixtyFour', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
scope: 'isolate',
|
||||
link: function ($scope, $elem, attrs, ngModel) {
|
||||
if ($elem.prop('multiple')) {
|
||||
throw new Error(multipleUsageErrorMessage);
|
||||
}
|
||||
|
||||
const maxSizeValidator = (dataUrl) => {
|
||||
return {
|
||||
errorKey: 'maxSize',
|
||||
isValid: attrs.max === '' || dataUrl.length <= parseInt(attrs.max)
|
||||
};
|
||||
};
|
||||
|
||||
const validators = [ maxSizeValidator ];
|
||||
|
||||
// produce fileContent$ whenever the $element 'change' event is triggered.
|
||||
const fileContent$ = Rx.fromEvent($elem, 'change', e => e).pipe(
|
||||
map(e => e.target.files),
|
||||
switchMap(files => {
|
||||
if (files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (files.length > 1) {
|
||||
throw new Error(multipleUsageErrorMessage);
|
||||
}
|
||||
|
||||
return createFileContent$(files[0]);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
// validate the content of the files after it is loaded
|
||||
const validations$ = fileContent$.pipe(
|
||||
map(fileContent => (
|
||||
validators.map(validator => validator(fileContent))
|
||||
))
|
||||
);
|
||||
|
||||
// push results from input/validation to the ngModel
|
||||
const unsubscribe = Rx
|
||||
.combineLatest(fileContent$, validations$)
|
||||
.subscribe(([ fileContent, validations ]) => {
|
||||
$scope.$evalAsync(() => {
|
||||
validations.forEach(validation => {
|
||||
ngModel.$setValidity(validation.errorKey, validation.isValid);
|
||||
});
|
||||
|
||||
if (validations.every(validation => validation.isValid)) {
|
||||
ngModel.$setViewValue(fileContent);
|
||||
}
|
||||
});
|
||||
}, (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
$scope.$on('destroy', unsubscribe);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('inputDatetime', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $elem, attrs, ngModel) {
|
||||
|
||||
const format = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
|
||||
$elem.after('<div class="input-datetime-format">' + format + '</div>');
|
||||
|
||||
// What should I make with the input from the user?
|
||||
const fromUser = function (text) {
|
||||
const parsed = moment(text, format);
|
||||
return parsed.isValid() ? parsed : undefined;
|
||||
};
|
||||
|
||||
// How should I present the data back to the user in the input field?
|
||||
const toUser = function (datetime) {
|
||||
return moment(datetime).format(format);
|
||||
};
|
||||
|
||||
ngModel.$parsers.push(fromUser);
|
||||
ngModel.$formatters.push(toUser);
|
||||
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { uiModules } from '../modules';
|
||||
// See https://github.com/elastic/elasticsearch/issues/6736
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.directive('validateIndexPattern', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, elem, attr, ngModel) {
|
||||
const illegalCharacters = ['\\', '/', '?', '"', '<', '>', '|', ' '];
|
||||
|
||||
const allowWildcard =
|
||||
!_.isUndefined(attr.validateIndexPatternAllowWildcard)
|
||||
&& attr.validateIndexPatternAllowWildcard !== 'false';
|
||||
|
||||
if (!allowWildcard) {
|
||||
illegalCharacters.push('*');
|
||||
}
|
||||
|
||||
const isValid = function (input) {
|
||||
if (input == null || input === '') return !attr.required === true;
|
||||
if (input === '.' || input === '..') return false;
|
||||
|
||||
const match = _.find(illegalCharacters, function (character) {
|
||||
return input.indexOf(character) >= 0;
|
||||
});
|
||||
|
||||
return !match;
|
||||
};
|
||||
|
||||
ngModel.$validators.indexPattern = function (modelValue, viewValue) {
|
||||
return isValid(viewValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -20,7 +20,6 @@
|
|||
import _ from 'lodash';
|
||||
import html from './doc_table.html';
|
||||
import { getSort } from './lib/get_sort';
|
||||
import '../directives/truncated';
|
||||
import '../directives/infinite_scroll';
|
||||
import './components/table_header';
|
||||
import './components/table_row';
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
import truncText from 'trunc-text';
|
||||
import truncHTML from 'trunc-html';
|
||||
import { uiModules } from '../modules';
|
||||
import truncatedTemplate from './partials/truncated.html';
|
||||
import { uiModules } from '../../modules';
|
||||
import truncatedTemplate from '../partials/truncated.html';
|
||||
import 'angular-sanitize';
|
||||
|
||||
const module = uiModules.get('kibana', ['ngSanitize']);
|
|
@ -26,7 +26,7 @@ import { banners } from './banners';
|
|||
import { Notifier } from './notifier';
|
||||
import template from './partials/toaster.html';
|
||||
import '../filters/markdown';
|
||||
import '../directives/truncated';
|
||||
import './directives/truncated';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
// IE specific fix for the datepicker to not collapse
|
||||
@include euiBreakpoint('m', 'l', 'xl') {
|
||||
.kbnQueryBar__datePickerWrapper {
|
||||
max-width: 40vw;
|
||||
flex-grow: 0;
|
||||
flex-basis: auto;
|
||||
// sass-lint:disable-block no-important
|
||||
flex-grow: 0 !important;
|
||||
flex-basis: auto !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
import {
|
||||
EuiConfirmModal,
|
||||
EuiIcon,
|
||||
EuiColorPicker,
|
||||
EuiIconTip,
|
||||
EuiCallOut,
|
||||
EuiSuperDatePicker,
|
||||
|
@ -42,8 +41,6 @@ app.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal))
|
|||
|
||||
app.directive('icon', reactDirective => reactDirective(EuiIcon));
|
||||
|
||||
app.directive('colorPicker', reactDirective => reactDirective(EuiColorPicker));
|
||||
|
||||
app.directive('iconTip', reactDirective => reactDirective(EuiIconTip, ['content', 'type', 'position', 'title', 'color']));
|
||||
|
||||
app.directive('callOut', reactDirective => reactDirective(EuiCallOut, ['title', 'color', 'size', 'iconType', 'children']));
|
||||
|
|
|
@ -104,7 +104,7 @@ describe('Vis-Editor-Agg-Params plugin directive', function () {
|
|||
aggFilter: aggFilter
|
||||
});
|
||||
|
||||
const customLabelElement = $elem.find('label:contains("Custom Label")');
|
||||
const customLabelElement = $elem.find('label:contains("Custom label")');
|
||||
expect(customLabelElement.length).to.be(1);
|
||||
});
|
||||
|
||||
|
@ -117,7 +117,7 @@ describe('Vis-Editor-Agg-Params plugin directive', function () {
|
|||
aggFilter: aggFilter
|
||||
});
|
||||
|
||||
const customLabelElement = $elem.find('label:contains("Custom Label")');
|
||||
const customLabelElement = $elem.find('label:contains("Custom label")');
|
||||
expect(customLabelElement.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,32 +17,82 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { isFunction } from 'lodash';
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { uiModules } from '../../../modules';
|
||||
import { AggParamReactWrapper } from './agg_param_react_wrapper';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.directive('visAggParamReactWrapper', reactDirective => reactDirective(wrapInI18nContext(AggParamReactWrapper), [
|
||||
['agg', { watchDepth: 'collection' }],
|
||||
['aggParam', { watchDepth: 'reference' }],
|
||||
['paramEditor', { wrapApply: false }],
|
||||
['onChange', { watchDepth: 'reference' }],
|
||||
'value',
|
||||
]))
|
||||
.directive('visAggParamEditor', function (config) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
// We can't use scope binding here yet, since quiet a lot of child directives arbitrary access
|
||||
// parent scope values right now. So we cannot easy change this, until we remove the whole directive.
|
||||
scope: true,
|
||||
template: function ($el) {
|
||||
require: '?^ngModel',
|
||||
template: function ($el, attrs) {
|
||||
if (attrs.editorComponent) {
|
||||
// Why do we need the `ng-if` here?
|
||||
// Short answer: Preventing black magic
|
||||
// Longer answer: The way this component is mounted in agg_params.js (by manually compiling)
|
||||
// and adding to some array, once you switch an aggregation type, this component will once
|
||||
// render once with a "broken state" (something like new aggParam, but still old template),
|
||||
// before agg_params.js actually removes it from the DOM and create a correct version with
|
||||
// the correct template. That ng-if check prevents us from crashing during that broken render.
|
||||
return `<vis-agg-param-react-wrapper
|
||||
ng-if="editorComponent"
|
||||
param-editor="editorComponent"
|
||||
agg="agg"
|
||||
agg-param="aggParam"
|
||||
on-change="onChange"
|
||||
value="paramValue"
|
||||
></vis-agg-param-react-wrapper>`;
|
||||
}
|
||||
|
||||
return $el.html();
|
||||
},
|
||||
link: {
|
||||
pre: function ($scope, $el, attr) {
|
||||
$scope.$bind('aggParam', attr.aggParam);
|
||||
$scope.$bind('agg', attr.agg);
|
||||
$scope.$bind('editorComponent', attr.editorComponent);
|
||||
},
|
||||
post: function ($scope) {
|
||||
post: function ($scope, $el, attr, ngModelCtrl) {
|
||||
$scope.config = config;
|
||||
|
||||
$scope.optionEnabled = function (option) {
|
||||
if (option && _.isFunction(option.enabled)) {
|
||||
if (option && isFunction(option.enabled)) {
|
||||
return option.enabled($scope.agg);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (attr.editorComponent) {
|
||||
$scope.$watch('agg.params[aggParam.name]', (value) => {
|
||||
// Whenever the value of the parameter changed (e.g. by a reset or actually by calling)
|
||||
// we store the new value in $scope.paramValue, which will be passed as a new value to the react component.
|
||||
$scope.paramValue = value;
|
||||
}, true);
|
||||
}
|
||||
|
||||
$scope.onChange = (value) => {
|
||||
// This is obviously not a good code quality, but without using scope binding (which we can't see above)
|
||||
// to bind function values, this is right now the best temporary fix, until all of this will be gone.
|
||||
$scope.$parent.onParamChange($scope.agg, $scope.aggParam.name, value);
|
||||
|
||||
if(ngModelCtrl) {
|
||||
ngModelCtrl.$setDirty();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { AggParam } from '../../../agg_types';
|
||||
import { AggConfig } from '../../agg_config';
|
||||
|
||||
interface AggParamEditorProps<T> {
|
||||
agg: AggConfig;
|
||||
aggParam: AggParam;
|
||||
value: T;
|
||||
setValue(value: T): void;
|
||||
}
|
||||
|
||||
export { AggParamEditorProps };
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AggParam } from '../../../agg_types';
|
||||
import { AggConfig } from '../../agg_config';
|
||||
import { AggParamEditorProps } from './agg_param_editor_props';
|
||||
|
||||
interface AggParamReactWrapperProps<T> {
|
||||
agg: AggConfig;
|
||||
aggParam: AggParam;
|
||||
paramEditor: React.FunctionComponent<AggParamEditorProps<T>>;
|
||||
value: T;
|
||||
onChange(value: T): void;
|
||||
}
|
||||
|
||||
function AggParamReactWrapper<T>(props: AggParamReactWrapperProps<T>) {
|
||||
const { agg, aggParam, paramEditor: ParamEditor, onChange, value } = props;
|
||||
return <ParamEditor value={value} setValue={onChange} aggParam={aggParam} agg={agg} />;
|
||||
}
|
||||
|
||||
export { AggParamReactWrapper };
|
|
@ -18,18 +18,18 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import { has, get } from 'lodash';
|
||||
import aggSelectHtml from './agg_select.html';
|
||||
import advancedToggleHtml from './advanced_toggle.html';
|
||||
import '../../../filters/match_any';
|
||||
import './agg_param';
|
||||
import { get, has } from 'lodash';
|
||||
import { aggTypes } from '../../../agg_types';
|
||||
import { uiModules } from '../../../modules';
|
||||
import { documentationLinks } from '../../../documentation_links/documentation_links';
|
||||
import aggParamsTemplate from './agg_params.html';
|
||||
import { aggTypeFilters } from '../../../agg_types/filter';
|
||||
import { editorConfigProviders } from '../config/editor_config_providers';
|
||||
import { aggTypeFieldFilters } from '../../../agg_types/param_types/filter';
|
||||
import { documentationLinks } from '../../../documentation_links/documentation_links';
|
||||
import '../../../filters/match_any';
|
||||
import { uiModules } from '../../../modules';
|
||||
import { editorConfigProviders } from '../config/editor_config_providers';
|
||||
import advancedToggleHtml from './advanced_toggle.html';
|
||||
import './agg_param';
|
||||
import aggParamsTemplate from './agg_params.html';
|
||||
import aggSelectHtml from './agg_select.html';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
|
@ -56,6 +56,12 @@ uiModules
|
|||
updateEditorConfig('default');
|
||||
});
|
||||
|
||||
$scope.onParamChange = (agg, paramName, value) => {
|
||||
if(agg.params[paramName] !== value) {
|
||||
agg.params[paramName] = value;
|
||||
}
|
||||
};
|
||||
|
||||
function updateEditorConfig(property = 'fixedValue') {
|
||||
$scope.editorConfig = editorConfigProviders.getConfigForAgg(
|
||||
aggTypes.byType[$scope.groupName],
|
||||
|
@ -185,23 +191,36 @@ uiModules
|
|||
// build HTML editor given an aggParam and index
|
||||
function getAggParamHTML(param, idx) {
|
||||
// don't show params without an editor
|
||||
if (!param.editor) {
|
||||
if (!param.editor && !param.editorComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = {
|
||||
'agg-param': 'agg.type.params[' + idx + ']'
|
||||
'agg-param': 'agg.type.params[' + idx + ']',
|
||||
'agg': 'agg',
|
||||
};
|
||||
|
||||
if (param.advanced) {
|
||||
attrs['ng-show'] = 'advancedToggled';
|
||||
}
|
||||
|
||||
if (param.editorComponent) {
|
||||
attrs['editor-component'] = `agg.type.params[${idx}].editorComponent`;
|
||||
// The form should interact with reactified components as well.
|
||||
// So we set the ng-model (using a random ng-model variable) to have the method to set dirty
|
||||
// inside the agg_param.js directive, which can get access to the ngModelController to manipulate it.
|
||||
attrs['ng-model'] = normalizeModelName(`_internalNgModelState${$scope.agg.id}${param.name}`);
|
||||
}
|
||||
|
||||
return $('<vis-agg-param-editor>')
|
||||
.attr(attrs)
|
||||
.append(param.editor)
|
||||
.get(0);
|
||||
}
|
||||
|
||||
function normalizeModelName(modelName = '') {
|
||||
return modelName.replace('-', '_');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
20
src/legacy/ui/public/vis/editors/default/index.ts
Normal file
20
src/legacy/ui/public/vis/editors/default/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { AggParamEditorProps } from './agg_param_editor_props';
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue