Merge remote-tracking branch 'origin/master' into feature/merge-code

This commit is contained in:
Fuyao Zhao 2019-03-08 00:40:10 -08:00
commit 6a1fc64c39
392 changed files with 13924 additions and 5739 deletions

View file

@ -7,6 +7,7 @@ source src/dev/ci_setup/setup.sh;
# download es snapshots
node scripts/es snapshot --download-only;
node scripts/es snapshot --license=oss --download-only;
# download reporting browsers
cd "x-pack";

View file

@ -31,6 +31,7 @@
"xpack.infra": "x-pack/plugins/infra",
"xpack.kueryAutocomplete": "x-pack/plugins/kuery_autocomplete",
"xpack.licenseMgmt": "x-pack/plugins/license_management",
"xpack.maps": "x-pack/plugins/maps",
"xpack.ml": "x-pack/plugins/ml",
"xpack.logstash": "x-pack/plugins/logstash",
"xpack.main": "x-pack/plugins/xpack_main",

View file

@ -18,6 +18,10 @@
# default to `true` starting in Kibana 7.0.
#server.rewriteBasePath: false
# Specifies the default route when opening Kibana. You can use this setting to modify
# the landing page when opening Kibana.
#server.defaultRoute: /app/kibana
# The maximum payload size in bytes for incoming server requests.
#server.maxPayloadBytes: 1048576
@ -36,9 +40,6 @@
# dashboards. Kibana creates a new index if the index doesn't already exist.
#kibana.index: ".kibana"
# The default application to load.
#kibana.defaultAppId: "home"
# If your Elasticsearch is protected with basic authentication, these settings provide
# the username and password that the Kibana server uses to perform maintenance on the Kibana
# index at startup. Your Kibana users still need to authenticate with Elasticsearch, which

View file

@ -20,7 +20,7 @@ Many Kibana developers hang out on `irc.freenode.net` in the `#kibana` channel.
[float]
==== Plugin Generator
It is recommended that you kick-start your plugin by generating it with the {repo}tree/{branch}/packages/kbn-plugin-generator[Kibana Plugin Generator]. Run the following within the Kibana repo and you will be asked a couple questions, see some progress bars, and have a freshly generated plugin ready for you to play within Kibana's sibling `kibana-extra` folder.
It is recommended that you kick-start your plugin by generating it with the {repo}tree/{branch}/packages/kbn-plugin-generator[Kibana Plugin Generator]. Run the following within the Kibana repo and you will be asked a couple questions, see some progress bars, and have a freshly generated plugin ready for you to play within Kibana's `plugins` folder.
["source","shell"]
-----------
@ -31,14 +31,15 @@ node scripts/generate_plugin my_plugin_name # replace "my_plugin_name" with your
[float]
==== Directory structure for plugins
The Kibana directory must be named `kibana`, and your plugin directory must be located within the sibling `kibana-extra` folder, for example:
The Kibana directory must be named `kibana`, and your plugin directory should be located in the root of `kibana` in a `plugins` directory, for example:
["source","shell"]
-----------
.
├── kibana
├── kibana-extra/foo-plugin
└── kibana-extra/bar-plugin
└── kibana
└── plugins
├── foo-plugin
└── bar-plugin
-----------
[float]

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

View file

@ -1,14 +1,47 @@
[[upgrade-assistant]]
== Upgrade Assistant
The Upgrade Assistant helps you prepare for your upgrade to {es} 8.0.
To access the assistant, go to *Management > 8.0 Upgrade Assistant*.
The Upgrade Assistant helps you prepare for your upgrade to {es} 9.0.
To access the assistant, go to *Management > 9.0 Upgrade Assistant*.
The assistant identifies the deprecated settings in your cluster and indices
and guides you through the process of resolving issues, including reindexing.
Before upgrading to Elasticsearch 8.0, make sure that you are using the final
7.x minor release to see the most up-to-date deprecation issues.
Before upgrading to Elasticsearch 9.0, make sure that you are using the final
8.x minor release to see the most up-to-date deprecation issues.
[float]
=== Reindexing
The *Indices* page lists the indices that are incompatible with the next
major version of {es}. You can initiate a reindex to resolve the issues.
[role="screenshot"]
image::images/management-upgrade-assistant-8.0.png[]
image::images/management-upgrade-assistant-9.0.png[]
For a preview of how the data will change during the reindex, select the
index name. A warning appears if the index requires destructive changes.
Back up your index, then proceed with the reindex by accepting each breaking change.
You can follow the progress as the Upgrade Assistant makes the index read-only,
creates a new index, reindexes the documents, and creates an alias that points
from the old index to the new one.
If the reindexing fails or is cancelled, the changes are rolled back, the
new index is deleted, and the original index becomes writable. An error
message explains the reason for the failure.
You can reindex multiple indices at a time, but keep an eye on the
{es} metrics, including CPU usage, memory pressure, and disk usage. If a
metric is so high it affects query performance, cancel the reindex and
continue by reindexing fewer indices at a time.
Additional considerations:
* During a reindex of a Watcher (`.watches`) index, the Watcher process
pauses and no alerts are triggered.
* During a reindex of a Machine Learning (`.ml-state`) index, the Machine
Learning job pauses and models are not trained or updated.

View file

@ -95,7 +95,7 @@
},
"dependencies": {
"@elastic/datemath": "5.0.2",
"@elastic/eui": "7.1.0",
"@elastic/eui": "9.0.2",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.2",
@ -153,7 +153,7 @@
"globby": "^8.0.1",
"good-squeeze": "2.1.0",
"h2o2": "^8.1.2",
"handlebars": "4.0.5",
"handlebars": "4.0.13",
"hapi": "^17.5.3",
"hjson": "3.1.0",
"hoek": "^5.0.4",

View file

@ -33,7 +33,8 @@ exports.getKibanaPath = function(config, projectRoot) {
if (inConfig && config.kibanaPath !== '.') {
throw new Error(
'The `kibanaPath` option has been removed from `eslint-import-resolver-kibana`. ' +
'During development your plugin must live in `../kibana-extra/{pluginName}` ' +
'During development your plugin must live in `./plugins/{pluginName}` ' +
'inside the Kibana folder or `../kibana-extra/{pluginName}` ' +
'relative to the Kibana folder to work with this package.'
);
}

View file

@ -18,7 +18,7 @@ To target the current development version of Kibana just use the default `maste
```sh
node scripts/generate_plugin my_plugin_name
# generates a plugin in `../kibana-extra/my_plugin_name`
# generates a plugin in `plugins/my_plugin_name`
```
To target 6.3, use the `6.x` branch (until the `6.3` branch is created).

View file

@ -42,7 +42,7 @@ exports.run = function run(argv) {
dedent(chalk`
{dim usage:} node scripts/generate-plugin {bold [name]}
generate a fresh Kibana plugin in the ../kibana-extra/ directory
generate a fresh Kibana plugin in the plugins/ directory
`) + '\n'
);
process.exit(1);
@ -50,8 +50,8 @@ exports.run = function run(argv) {
const name = options._[0];
const template = resolve(__dirname, './sao_template');
const kibanaExtra = resolve(__dirname, '../../../kibana-extra');
const targetPath = resolve(kibanaExtra, snakeCase(name));
const kibanaPlugins = resolve(__dirname, '../../plugins');
const targetPath = resolve(kibanaPlugins, snakeCase(name));
sao({
template: template,

View file

@ -102,7 +102,7 @@ module.exports = function({ name }) {
cwd: KBN_DIR,
stdio: 'inherit',
}).then(() => {
const dir = relative(process.cwd(), resolve(KBN_DIR, `../kibana-extra`, snakeCase(name)));
const dir = relative(process.cwd(), resolve(KBN_DIR, 'plugins', snakeCase(name)));
log.success(chalk`🎉

View file

@ -88,15 +88,16 @@ are running inside of.
```
This works because we moved to a strict location of Kibana plugins,
`../kibana-extra/{pluginName}` relative to Kibana. This is one of the reasons we
wanted to move towards a setup that looks like this:
`./plugins/{pluginName}` inside of Kibana, or `../kibana-extra/{pluginName}`
relative to Kibana. This is one of the reasons we wanted to move towards a setup
that looks like this:
```
elastic
── kibana
└── kibana-extra
├── kibana-canvas
└── x-pack-kibana
── kibana
└── plugins
├── kibana-canvas
└── x-pack-kibana
```
Relying on `link:` style dependencies means we no longer need to `npm publish`
@ -119,11 +120,12 @@ yarn kbn bootstrap
```
By default, `@kbn/pm` will bootstrap all packages within Kibana, plus all
Kibana plugins located in `../kibana-extra`. There are several options for
skipping parts of this, e.g. to skip bootstrapping of Kibana plugins:
Kibana plugins located in `./plugins` or `../kibana-extra`. There are several
options for skipping parts of this, e.g. to skip bootstrapping of Kibana
plugins:
```
yarn kbn bootstrap --skip-kibana-extra
yarn kbn bootstrap --skip-kibana-plugins
```
Or just skip few selected packages:
@ -152,7 +154,7 @@ yarn kbn run build
```
And if needed, you can skip packages in the same way as for bootstrapping, e.g.
with `--exclude` and `--skip-kibana-extra`:
with `--exclude` and `--skip-kibana-plugins`:
```
yarn kbn run build --exclude kibana

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@ function help() {
usage: kbn <command> [<args>]
By default commands are run for Kibana itself, all packages in the 'packages/'
folder and for all plugins in '../kibana-extra'.
folder and for all plugins in './plugins' and '../kibana-extra'.
Available commands:
@ -43,10 +43,10 @@ function help() {
Global options:
-e, --exclude Exclude specified project. Can be specified multiple times to exclude multiple projects, e.g. '-e kibana -e @kbn/pm'.
-i, --include Include only specified projects. If left unspecified, it defaults to including all projects.
--oss Do not include the x-pack when running command.
--skip-kibana-extra Filter all plugins in ../kibana-extra when running command.
-e, --exclude Exclude specified project. Can be specified multiple times to exclude multiple projects, e.g. '-e kibana -e @kbn/pm'.
-i, --include Include only specified projects. If left unspecified, it defaults to including all projects.
--oss Do not include the x-pack when running command.
--skip-kibana-plugins Filter all plugins in ./plugins and ../kibana-extra when running command.
`);
}

View file

@ -20,7 +20,7 @@
import { resolve } from 'path';
export interface IProjectPathOptions {
'skip-kibana-extra'?: boolean;
'skip-kibana-plugins'?: boolean;
oss?: boolean;
}
@ -28,7 +28,7 @@ export interface IProjectPathOptions {
* Returns all the paths where plugins are located
*/
export function getProjectPaths(rootPath: string, options: IProjectPathOptions) {
const skipKibanaExtra = Boolean(options['skip-kibana-extra']);
const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']);
const ossOnly = Boolean(options.oss);
const projectPaths = [rootPath, resolve(rootPath, 'packages/*')];
@ -49,10 +49,13 @@ export function getProjectPaths(rootPath: string, options: IProjectPathOptions)
projectPaths.push(resolve(rootPath, 'x-pack/plugins/*'));
}
if (!skipKibanaExtra) {
if (!skipKibanaPlugins) {
projectPaths.push(resolve(rootPath, '../kibana-extra/*'));
projectPaths.push(resolve(rootPath, '../kibana-extra/*/packages/*'));
projectPaths.push(resolve(rootPath, '../kibana-extra/*/plugins/*'));
projectPaths.push(resolve(rootPath, 'plugins/*'));
projectPaths.push(resolve(rootPath, 'plugins/*/packages/*'));
projectPaths.push(resolve(rootPath, 'plugins/*/plugins/*'));
}
return projectPaths;

View file

@ -21,7 +21,8 @@ import { isLinkDependency } from '../utils/package_json';
import { Project } from '../utils/project';
/**
* All external projects are located within `../kibana-extra/{plugin}` relative
* All external projects are located within `./plugins/{plugin}` relative
* to the Kibana root directory or `../kibana-extra/{plugin}` relative
* to Kibana itself.
*/
const isKibanaDep = (depVersion: string) => depVersion.includes('../../kibana/');

View file

@ -0,0 +1,4 @@
{
"name": "corge",
"version": "1.0.0"
}

View file

@ -17,7 +17,10 @@
* under the License.
*/
import { resolve } from 'path';
import { mkdir, symlink } from 'fs';
import { join, resolve } from 'path';
import rmdir from 'rimraf';
import { promisify } from 'util';
import { getProjectPaths } from '../config';
import { Project } from './project';
@ -31,8 +34,20 @@ import {
} from './projects';
const rootPath = resolve(`${__dirname}/__fixtures__/kibana`);
const rootPlugins = join(rootPath, 'plugins');
describe('#getProjects', () => {
beforeAll(async () => {
await promisify(mkdir)(rootPlugins);
return promisify(symlink)(
join(__dirname, '__fixtures__/symlinked-plugins/corge'),
join(rootPlugins, 'corge')
);
});
afterAll(() => promisify(rmdir)(rootPlugins));
test('find all packages in the packages directory', async () => {
const projects = await getProjects(rootPath, ['packages/*']);
@ -68,7 +83,15 @@ describe('#getProjects', () => {
const projectPaths = getProjectPaths(rootPath, {});
const projects = await getProjects(rootPath, projectPaths);
const expectedProjects = ['kibana', 'bar', 'foo', 'with-additional-projects', 'quux', 'baz'];
const expectedProjects = [
'kibana',
'bar',
'foo',
'with-additional-projects',
'quux',
'baz',
'bar',
];
expect([...projects.keys()]).toEqual(expect.arrayContaining(expectedProjects));
expect(projects.size).toBe(expectedProjects.length);
@ -85,7 +108,12 @@ describe('#getProjects', () => {
exclude: ['foo', 'bar', 'baz'],
});
expect([...projects.keys()].sort()).toEqual(['kibana', 'quux', 'with-additional-projects']);
expect([...projects.keys()].sort()).toEqual([
'corge',
'kibana',
'quux',
'with-additional-projects',
]);
});
test('ignores unknown projects specified in `exclude` filter', async () => {
@ -95,6 +123,7 @@ describe('#getProjects', () => {
expect([...projects.keys()].sort()).toEqual([
'baz',
'corge',
'foo',
'kibana',
'quux',
@ -137,7 +166,7 @@ describe('#getProjects', () => {
test('does not return any project if `exclude` filter is specified for all projects', async () => {
const projects = await getProjects(rootPath, projectPaths, {
exclude: ['kibana', 'bar', 'foo', 'with-additional-projects', 'quux', 'baz'],
exclude: ['kibana', 'bar', 'corge', 'foo', 'with-additional-projects', 'quux', 'baz'],
});
expect(projects.size).toBe(0);

View file

@ -54,18 +54,20 @@ export interface LegacyPlatformParams {
export class LegacyPlatformService {
constructor(private readonly params: LegacyPlatformParams) {}
public start({
i18n,
injectedMetadata,
fatalErrors,
notifications,
http,
basePath,
uiSettings,
chrome,
}: Deps) {
public start(deps: Deps) {
const {
i18n,
injectedMetadata,
fatalErrors,
notifications,
http,
basePath,
uiSettings,
chrome,
} = deps;
// Inject parts of the new platform into parts of the legacy platform
// so that legacy APIs/modules can mimic their new platform counterparts
require('ui/new_platform').__newPlatformInit__(deps);
require('ui/metadata').__newPlatformInit__(injectedMetadata.getLegacyMetadata());
require('ui/i18n').__newPlatformInit__(i18n.Context);
require('ui/notify/fatal_error').__newPlatformInit__(fatalErrors);

View file

@ -0,0 +1,50 @@
/*
* 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 { 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> = {
atPath: jest.fn(),
getConfig$: jest.fn(),
optionalAtPath: jest.fn(),
getUsedPaths: jest.fn(),
getUnusedPaths: jest.fn(),
isEnabledAtPath: jest.fn(),
};
mocked.atPath.mockReturnValue(new BehaviorSubject({}));
mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter({})));
mocked.getUsedPaths.mockResolvedValue([]);
mocked.getUnusedPaths.mockResolvedValue([]);
mocked.isEnabledAtPath.mockResolvedValue(true);
return mocked;
};
export const configServiceMock = {
create: createConfigServiceMock,
};

View file

@ -23,16 +23,17 @@ import { BehaviorSubject } from 'rxjs';
import { first } from 'rxjs/operators';
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../legacy/utils/package_json', () => ({ pkg: mockPackage }));
jest.mock('../../../../package.json', () => mockPackage);
import { schema, Type, TypeOf } from '@kbn/config-schema';
import { ConfigService, Env, ObjectToConfigAdapter } from '.';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { getEnvOptions } from './__mocks__/env';
const emptyArgv = getEnvOptions();
const defaultEnv = new Env('/kibana', emptyArgv);
const logger = loggingServiceMock.create();
test('returns config at path as observable', async () => {
const config$ = new BehaviorSubject(new ObjectToConfigAdapter({ key: 'foo' }));

View file

@ -30,7 +30,7 @@ jest.mock('path', () => ({
}));
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../legacy/utils/package_json', () => ({ pkg: mockPackage }));
jest.mock('../../../../package.json', () => mockPackage);
import { Env } from '.';
import { getEnvOptions } from './__mocks__/env';

View file

@ -20,7 +20,9 @@
import { resolve } from 'path';
import process from 'process';
import { pkg } from '../../../legacy/utils/package_json';
// `require` is necessary for this to work inside x-pack code as well
// tslint:disable no-var-requires
const pkg = require('../../../../package.json');
export interface PackageInfo {
version: string;

View file

@ -17,8 +17,8 @@
* under the License.
*/
import { ConfigService, Env } from '../server/config';
import { LoggerFactory } from '../server/logging';
import { ConfigService, Env } from './config';
import { LoggerFactory } from './logging';
/**
* Groups all main Kibana's core modules/systems/services that are consumed in a

View file

@ -39,9 +39,10 @@ jest.mock('./elasticsearch_client_config', () => ({
import { errors } from 'elasticsearch';
import { get } from 'lodash';
import { Logger } from '../logging';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { ClusterClient } from './cluster_client';
const logger = loggingServiceMock.create();
afterEach(() => jest.clearAllMocks());
test('#constructor creates client with parsed config', () => {

View file

@ -21,12 +21,12 @@ const mockReadFileSync = jest.fn();
jest.mock('fs', () => ({ readFileSync: mockReadFileSync }));
import { duration } from 'moment';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import {
ElasticsearchClientConfig,
parseElasticsearchClientConfig,
} from './elasticsearch_client_config';
const logger = loggingServiceMock.create();
afterEach(() => jest.clearAllMocks());
test('parses minimally specified config', () => {
@ -365,7 +365,7 @@ describe('#log', () => {
expect(typeof esLogger.close).toBe('function');
expect(logger.mockCollect()).toMatchInlineSnapshot(`
expect(loggingServiceMock.collect(logger)).toMatchInlineSnapshot(`
Object {
"debug": Array [],
"error": Array [
@ -411,7 +411,7 @@ Object {
expect(typeof esLogger.close).toBe('function');
expect(logger.mockCollect()).toMatchInlineSnapshot(`
expect(loggingServiceMock.collect(logger)).toMatchInlineSnapshot(`
Object {
"debug": Array [
Array [

View file

@ -0,0 +1,57 @@
/*
* 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 { ClusterClient } from './cluster_client';
import { ElasticsearchConfig } from './elasticsearch_config';
import { ElasticsearchService, ElasticsearchServiceStart } from './elasticsearch_service';
const createStartContractMock = () => {
const startContract: ElasticsearchServiceStart = {
legacy: {
config$: new BehaviorSubject({} as ElasticsearchConfig),
},
createClient: jest.fn(),
adminClient$: new BehaviorSubject({} as ClusterClient),
dataClient$: new BehaviorSubject({} as ClusterClient),
};
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> = {
start: jest.fn(),
stop: jest.fn(),
};
mocked.start.mockResolvedValue(createStartContractMock());
mocked.stop.mockResolvedValue();
return mocked;
};
export const elasticsearchServiceMock = {
create: createMock,
createStartContract: createStartContractMock,
};

View file

@ -23,10 +23,10 @@ const MockClusterClient = jest.fn();
jest.mock('./cluster_client', () => ({ ClusterClient: MockClusterClient }));
import { BehaviorSubject, combineLatest } from 'rxjs';
import { CoreContext } from '../../types';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { logger } from '../logging/__mocks__';
import { CoreContext } from '../core_context';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { ElasticsearchConfig } from './elasticsearch_config';
import { ElasticsearchService } from './elasticsearch_service';
@ -34,6 +34,7 @@ let elasticsearchService: ElasticsearchService;
let configService: ConfigService;
let env: Env;
let coreContext: CoreContext;
const logger = loggingServiceMock.create();
beforeEach(() => {
env = Env.createDefault(getEnvOptions());

View file

@ -19,7 +19,8 @@
import { ConnectableObservable, Observable, Subscription } from 'rxjs';
import { filter, map, publishReplay, switchMap } from 'rxjs/operators';
import { CoreContext, CoreService } from '../../types';
import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { ClusterClient } from './cluster_client';
import { ElasticsearchClientConfig } from './elasticsearch_client_config';

View file

@ -20,7 +20,7 @@
export { ElasticsearchServiceStart } from './elasticsearch_service';
export { CallAPIOptions, ClusterClient } from './cluster_client';
import { CoreContext } from '../../types';
import { CoreContext } from '../core_context';
import { ElasticsearchService } from './elasticsearch_service';
/** @internal */

View file

@ -28,7 +28,7 @@ import supertest from 'supertest';
import { ByteSizeValue } from '@kbn/config-schema';
import { HttpConfig, Router } from '.';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { HttpServer } from './http_server';
const chance = new Chance();
@ -36,6 +36,7 @@ const chance = new Chance();
let server: HttpServer;
let config: HttpConfig;
const logger = loggingServiceMock.create();
beforeEach(() => {
config = {
host: '127.0.0.1',
@ -49,7 +50,7 @@ beforeEach(() => {
afterEach(async () => {
await server.stop();
logger.mockClear();
jest.clearAllMocks();
});
test('listening after started', async () => {

View file

@ -0,0 +1,51 @@
/*
* 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 { Server, ServerOptions } from 'hapi';
import { HttpService } from './http_service';
const createStartContractMock = () => {
const startContract = {
// we can mock some hapi server method when we need it
server: {} as Server,
options: {} as ServerOptions,
};
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> = {
start: jest.fn(),
stop: jest.fn(),
registerRouter: jest.fn(),
};
mocked.start.mockResolvedValue(createStartContractMock());
return mocked;
};
export const httpServiceMock = {
create: createHttpServiceMock,
createStartContract: createStartContractMock,
};

View file

@ -26,11 +26,12 @@ jest.mock('./http_server', () => ({
import { noop } from 'lodash';
import { BehaviorSubject } from 'rxjs';
import { HttpConfig, HttpService, Router } from '.';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
beforeEach(() => {
logger.mockClear();
mockHttpServer.mockClear();
const logger = loggingServiceMock.create();
afterEach(() => {
jest.clearAllMocks();
});
test('creates and starts http server', async () => {
@ -75,7 +76,7 @@ test('logs error if already started', async () => {
await service.start();
expect(logger.mockCollect()).toMatchSnapshot();
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
});
test('stops http server', async () => {
@ -121,7 +122,7 @@ test('register route handler', () => {
expect(httpServer.registerRouter).toHaveBeenCalledTimes(1);
expect(httpServer.registerRouter).toHaveBeenLastCalledWith(router);
expect(logger.mockCollect()).toMatchSnapshot();
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
});
test('throws if registering route handler after http server is started', () => {
@ -143,7 +144,7 @@ test('throws if registering route handler after http server is started', () => {
service.registerRouter(router);
expect(httpServer.registerRouter).toHaveBeenCalledTimes(0);
expect(logger.mockCollect()).toMatchSnapshot();
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
});
test('returns http server contract on start', async () => {

View file

@ -27,7 +27,7 @@ import supertest from 'supertest';
import { ByteSizeValue } from '@kbn/config-schema';
import { HttpConfig } from '.';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { HttpsRedirectServer } from './https_redirect_server';
const chance = new Chance();
@ -50,12 +50,11 @@ beforeEach(() => {
},
} as HttpConfig;
server = new HttpsRedirectServer(logger.get());
server = new HttpsRedirectServer(loggingServiceMock.create().get());
});
afterEach(async () => {
await server.stop();
logger.mockClear();
});
test('throws if SSL is not enabled', async () => {

View file

@ -16,10 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
const mockHttpService = { start: jest.fn(), stop: jest.fn(), registerRouter: jest.fn() };
import { httpServiceMock } from './http/http_service.mock';
const httpService = httpServiceMock.create();
jest.mock('./http/http_service', () => ({
HttpService: jest.fn(() => mockHttpService),
HttpService: jest.fn(() => httpService),
}));
const mockPluginsService = { start: jest.fn(), stop: jest.fn() };
@ -27,9 +27,10 @@ jest.mock('./plugins/plugins_service', () => ({
PluginsService: jest.fn(() => mockPluginsService),
}));
const mockElasticsearchService = { start: jest.fn(), stop: jest.fn() };
import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
const elasticsearchService = elasticsearchServiceMock.create();
jest.mock('./elasticsearch/elasticsearch_service', () => ({
ElasticsearchService: jest.fn(() => mockElasticsearchService),
ElasticsearchService: jest.fn(() => elasticsearchService),
}));
const mockLegacyService = { start: jest.fn(), stop: jest.fn() };
@ -41,22 +42,26 @@ import { BehaviorSubject } from 'rxjs';
import { Server } from '.';
import { Env } from './config';
import { getEnvOptions } from './config/__mocks__/env';
import { logger } from './logging/__mocks__';
import { loggingServiceMock } from './logging/logging_service.mock';
const mockConfigService = { atPath: jest.fn(), getUnusedPaths: jest.fn().mockReturnValue([]) };
import { configServiceMock } from './config/config_service.mock';
const configService = configServiceMock.create();
const env = new Env('.', getEnvOptions());
const logger = loggingServiceMock.create();
beforeEach(() => {
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
});
afterEach(() => {
logger.mockClear();
mockConfigService.atPath.mockReset();
mockHttpService.start.mockReset();
mockHttpService.stop.mockReset();
mockElasticsearchService.start.mockReset();
mockElasticsearchService.stop.mockReset();
jest.clearAllMocks();
configService.atPath.mockReset();
httpService.start.mockReset();
httpService.stop.mockReset();
elasticsearchService.start.mockReset();
elasticsearchService.stop.mockReset();
mockPluginsService.start.mockReset();
mockPluginsService.stop.mockReset();
mockLegacyService.start.mockReset();
@ -64,65 +69,49 @@ afterEach(() => {
});
test('starts services on "start"', async () => {
const mockHttpServiceStart = { something: true };
mockHttpService.start.mockReturnValue(Promise.resolve(mockHttpServiceStart));
const mockElasticsearchServiceStart = { adminClient$: {} };
mockElasticsearchService.start.mockResolvedValue(mockElasticsearchServiceStart);
const mockPluginsServiceStart = new Map([['some-plugin', 'some-value']]);
mockPluginsService.start.mockReturnValue(Promise.resolve(mockPluginsServiceStart));
const server = new Server(mockConfigService as any, logger, env);
const server = new Server(configService as any, logger, env);
expect(mockHttpService.start).not.toHaveBeenCalled();
expect(mockElasticsearchService.start).not.toHaveBeenCalled();
expect(httpService.start).not.toHaveBeenCalled();
expect(elasticsearchService.start).not.toHaveBeenCalled();
expect(mockPluginsService.start).not.toHaveBeenCalled();
expect(mockLegacyService.start).not.toHaveBeenCalled();
await server.start();
expect(mockHttpService.start).toHaveBeenCalledTimes(1);
expect(mockElasticsearchService.start).toHaveBeenCalledTimes(1);
expect(httpService.start).toHaveBeenCalledTimes(1);
expect(elasticsearchService.start).toHaveBeenCalledTimes(1);
expect(mockPluginsService.start).toHaveBeenCalledTimes(1);
expect(mockPluginsService.start).toHaveBeenCalledWith({
elasticsearch: mockElasticsearchServiceStart,
});
expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
expect(mockLegacyService.start).toHaveBeenCalledWith({
elasticsearch: mockElasticsearchServiceStart,
http: mockHttpServiceStart,
plugins: mockPluginsServiceStart,
});
});
test('does not fail on "start" if there are unused paths detected', async () => {
mockConfigService.getUnusedPaths.mockReturnValue(['some.path', 'another.path']);
configService.getUnusedPaths.mockResolvedValue(['some.path', 'another.path']);
const server = new Server(mockConfigService as any, logger, env);
const server = new Server(configService as any, logger, env);
await expect(server.start()).resolves.toBeUndefined();
expect(logger.mockCollect()).toMatchSnapshot('unused paths logs');
expect(loggingServiceMock.collect(logger)).toMatchSnapshot('unused paths logs');
});
test('does not start http service is `autoListen:false`', async () => {
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
const server = new Server(mockConfigService as any, logger, env);
const server = new Server(configService as any, logger, env);
expect(mockLegacyService.start).not.toHaveBeenCalled();
await server.start();
expect(mockHttpService.start).not.toHaveBeenCalled();
expect(httpService.start).not.toHaveBeenCalled();
expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
expect(mockLegacyService.start).toHaveBeenCalledWith({});
});
test('does not start http service if process is dev cluster master', async () => {
const server = new Server(
mockConfigService as any,
configService as any,
logger,
new Env('.', getEnvOptions({ isDevClusterMaster: true }))
);
@ -131,28 +120,25 @@ test('does not start http service if process is dev cluster master', async () =>
await server.start();
expect(mockHttpService.start).not.toHaveBeenCalled();
expect(httpService.start).not.toHaveBeenCalled();
expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
expect(mockLegacyService.start).toHaveBeenCalledWith({});
});
test('stops services on "stop"', async () => {
const mockHttpServiceStart = { something: true };
mockHttpService.start.mockReturnValue(Promise.resolve(mockHttpServiceStart));
const server = new Server(mockConfigService as any, logger, env);
const server = new Server(configService as any, logger, env);
await server.start();
expect(mockHttpService.stop).not.toHaveBeenCalled();
expect(mockElasticsearchService.stop).not.toHaveBeenCalled();
expect(httpService.stop).not.toHaveBeenCalled();
expect(elasticsearchService.stop).not.toHaveBeenCalled();
expect(mockPluginsService.stop).not.toHaveBeenCalled();
expect(mockLegacyService.stop).not.toHaveBeenCalled();
await server.stop();
expect(mockHttpService.stop).toHaveBeenCalledTimes(1);
expect(mockElasticsearchService.stop).toHaveBeenCalledTimes(1);
expect(httpService.stop).toHaveBeenCalledTimes(1);
expect(elasticsearchService.stop).toHaveBeenCalledTimes(1);
expect(mockPluginsService.stop).toHaveBeenCalledTimes(1);
expect(mockLegacyService.stop).toHaveBeenCalledTimes(1);
});

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { CoreContext } from '../../types';
import { CoreContext } from '../core_context';
import { LegacyService } from './legacy_service';
/** @internal */

View file

@ -28,10 +28,11 @@ import { LegacyService } from '.';
// @ts-ignore: implicit any for JS file
import MockClusterManager from '../../../cli/cluster/cluster_manager';
import KbnServer from '../../../legacy/server/kbn_server';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
import { Config, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { configServiceMock } from '../config/config_service.mock';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { logger } from '../logging/__mocks__';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { PluginsServiceStart } from '../plugins/plugins_service';
import { LegacyPlatformProxy } from './legacy_platform_proxy';
@ -39,7 +40,6 @@ const MockKbnServer: jest.Mock<KbnServer> = KbnServer as any;
const MockLegacyPlatformProxy: jest.Mock<LegacyPlatformProxy> = LegacyPlatformProxy as any;
let legacyService: LegacyService;
let configService: jest.Mocked<ConfigService>;
let env: Env;
let config$: BehaviorSubject<Config>;
let startDeps: {
@ -47,8 +47,12 @@ let startDeps: {
http: any;
plugins: PluginsServiceStart;
};
const logger = loggingServiceMock.create();
let configService: ReturnType<typeof configServiceMock.create>;
beforeEach(() => {
env = Env.createDefault(getEnvOptions());
configService = configServiceMock.create();
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
@ -68,19 +72,14 @@ beforeEach(() => {
})
);
configService = {
getConfig$: jest.fn().mockReturnValue(config$),
atPath: jest.fn().mockReturnValue(new BehaviorSubject({})),
getUsedPaths: jest.fn().mockReturnValue(['foo.bar']),
} as any;
legacyService = new LegacyService({ env, logger, configService });
configService.getConfig$.mockReturnValue(config$);
configService.getUsedPaths.mockResolvedValue(['foo.bar']);
legacyService = new LegacyService({ env, logger, configService: configService as any });
});
afterEach(() => {
MockLegacyPlatformProxy.mockClear();
MockKbnServer.mockClear();
MockClusterManager.create.mockClear();
logger.mockClear();
jest.clearAllMocks();
});
describe('once LegacyService is started with connection info', () => {
@ -235,7 +234,7 @@ describe('once LegacyService is started with connection info', () => {
const [mockKbnServer] = MockKbnServer.mock.instances as Array<jest.Mocked<KbnServer>>;
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
expect(logger.mockCollect().error).toEqual([]);
expect(loggingServiceMock.collect(logger).error).toEqual([]);
const configError = new Error('something went wrong');
mockKbnServer.applyLoggingConfiguration.mockImplementation(() => {
@ -244,7 +243,7 @@ describe('once LegacyService is started with connection info', () => {
config$.next(new ObjectToConfigAdapter({ logging: { verbose: true } }));
expect(logger.mockCollect().error).toEqual([[configError]]);
expect(loggingServiceMock.collect(logger).error).toEqual([[configError]]);
});
test('logs error if config service fails.', async () => {
@ -252,13 +251,13 @@ describe('once LegacyService is started with connection info', () => {
const [mockKbnServer] = MockKbnServer.mock.instances;
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
expect(logger.mockCollect().error).toEqual([]);
expect(loggingServiceMock.collect(logger).error).toEqual([]);
const configError = new Error('something went wrong');
config$.error(configError);
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
expect(logger.mockCollect().error).toEqual([[configError]]);
expect(loggingServiceMock.collect(logger).error).toEqual([[configError]]);
});
test('proxy route abandons request processing and forwards it to the legacy Kibana', async () => {
@ -337,7 +336,7 @@ describe('once LegacyService is started in `devClusterMaster` mode', () => {
})
),
logger,
configService,
configService: configService as any,
});
await devClusterLegacyService.start({
@ -359,7 +358,7 @@ describe('once LegacyService is started in `devClusterMaster` mode', () => {
})
),
logger,
configService,
configService: configService as any,
});
await devClusterLegacyService.start({

View file

@ -20,8 +20,9 @@
import { Server as HapiServer } from 'hapi';
import { combineLatest, ConnectableObservable, EMPTY, Subscription } from 'rxjs';
import { first, map, mergeMap, publishReplay, tap } from 'rxjs/operators';
import { CoreContext, CoreService } from '../../types';
import { CoreService } from '../../types';
import { Config } from '../config';
import { CoreContext } from '../core_context';
import { DevConfig } from '../dev';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { BasePathProxyServer, HttpConfig, HttpServiceStart } from '../http';

View file

@ -1,61 +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.
*/
// Test helpers to simplify mocking logs and collecting all their outputs
const mockLog = {
debug: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
info: jest.fn(),
log: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
};
const mockClear = () => {
logger.get.mockClear();
mockLog.debug.mockClear();
mockLog.info.mockClear();
mockLog.warn.mockClear();
mockLog.error.mockClear();
mockLog.trace.mockClear();
mockLog.fatal.mockClear();
mockLog.log.mockClear();
};
const mockCollect = () => ({
debug: mockLog.debug.mock.calls,
error: mockLog.error.mock.calls,
fatal: mockLog.fatal.mock.calls,
info: mockLog.info.mock.calls,
log: mockLog.log.mock.calls,
trace: mockLog.trace.mock.calls,
warn: mockLog.warn.mock.calls,
});
export const logger = {
get: jest.fn((...context) => ({
context,
...mockLog,
})),
mockClear,
mockCollect,
mockLog,
};

View file

@ -0,0 +1,92 @@
/*
* 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.
*/
// Test helpers to simplify mocking logs and collecting all their outputs
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>;
const createLoggingServiceMock = () => {
const mockLog: MockedLogger = {
debug: jest.fn(),
error: jest.fn(),
fatal: jest.fn(),
info: jest.fn(),
log: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
};
const mocked: jest.Mocked<LoggingServiceContract> = {
get: jest.fn(),
asLoggerFactory: jest.fn(),
upgrade: jest.fn(),
stop: jest.fn(),
};
mocked.get.mockImplementation((...context) => ({
context,
...mockLog,
}));
mocked.asLoggerFactory.mockImplementation(() => createLoggingServiceMock());
mocked.stop.mockResolvedValue();
return mocked;
};
const collectLoggingServiceMock = (mocked: ReturnType<typeof createLoggingServiceMock>) => {
const mockLog = mocked.get() as MockedLogger;
return {
debug: mockLog.debug.mock.calls,
error: mockLog.error.mock.calls,
fatal: mockLog.fatal.mock.calls,
info: mockLog.info.mock.calls,
log: mockLog.log.mock.calls,
trace: mockLog.trace.mock.calls,
warn: mockLog.warn.mock.calls,
};
};
const clearLoggingServiceMock = (mocked: ReturnType<typeof createLoggingServiceMock>) => {
const mockLog = mocked.get() as MockedLogger;
mocked.get.mockClear();
mocked.asLoggerFactory.mockClear();
mocked.upgrade.mockClear();
mocked.stop.mockClear();
mockLog.debug.mockClear();
mockLog.info.mockClear();
mockLog.warn.mockClear();
mockLog.error.mockClear();
mockLog.trace.mockClear();
mockLog.fatal.mockClear();
mockLog.log.mockClear();
};
export const loggingServiceMock = {
create: createLoggingServiceMock,
collect: collectLoggingServiceMock,
clear: clearLoggingServiceMock,
};

View file

@ -27,14 +27,14 @@ jest.mock('fs', () => ({
}));
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../../legacy/utils/package_json', () => ({ pkg: mockPackage }));
jest.mock('../../../../../package.json', () => mockPackage);
import { resolve } from 'path';
import { BehaviorSubject } from 'rxjs';
import { first, map, toArray } from 'rxjs/operators';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config';
import { getEnvOptions } from '../../config/__mocks__/env';
import { logger } from '../../logging/__mocks__';
import { loggingServiceMock } from '../../logging/logging_service.mock';
import { Plugin } from '../plugin';
import { PluginsConfig } from '../plugins_config';
import { discover } from './plugins_discovery';
@ -45,6 +45,7 @@ const TEST_PLUGIN_SEARCH_PATHS = {
nonExistentKibanaExtra: resolve(process.cwd(), '..', 'kibana-extra'),
};
const logger = loggingServiceMock.create();
beforeEach(() => {
mockReaddir.mockImplementation((path, cb) => {
if (path === TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins) {

View file

@ -21,7 +21,7 @@ import { readdir, stat } from 'fs';
import { resolve } from 'path';
import { bindNodeCallback, from } from 'rxjs';
import { catchError, filter, map, mergeMap, shareReplay } from 'rxjs/operators';
import { CoreContext } from '../../../types';
import { CoreContext } from '../../core_context';
import { Logger } from '../../logging';
import { Plugin } from '../plugin';
import { createPluginInitializerContext } from '../plugin_context';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { CoreContext } from '../../types';
import { CoreContext } from '../core_context';
import { PluginsService } from './plugins_service';
/** @internal */

View file

@ -19,15 +19,16 @@
import { join } from 'path';
import { BehaviorSubject } from 'rxjs';
import { CoreContext } from '../../types';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { logger } from '../logging/__mocks__';
import { CoreContext } from '../core_context';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { Plugin, PluginManifest } from './plugin';
import { createPluginInitializerContext, createPluginStartContext } from './plugin_context';
const mockPluginInitializer = jest.fn();
const logger = loggingServiceMock.create();
jest.mock(
join('plugin-with-initializer-path', 'server'),
() => ({ plugin: mockPluginInitializer }),
@ -57,10 +58,9 @@ function createPluginManifest(manifestProps: Partial<PluginManifest> = {}): Plug
let configService: ConfigService;
let env: Env;
let coreContext: CoreContext;
let startDeps: { elasticsearch: ElasticsearchServiceStart };
const startDeps = { elasticsearch: elasticsearchServiceMock.createStartContract() };
beforeEach(() => {
env = Env.createDefault(getEnvOptions());
startDeps = { elasticsearch: { adminClient$: {}, dataClient$: {} } as any };
configService = new ConfigService(
new BehaviorSubject<Config>(new ObjectToConfigAdapter({ plugins: { initialize: true } })),

View file

@ -19,8 +19,8 @@
import { Type } from '@kbn/config-schema';
import { Observable } from 'rxjs';
import { CoreContext } from '../../types';
import { ConfigWithSchema, EnvironmentMode } from '../config';
import { CoreContext } from '../core_context';
import { ClusterClient } from '../elasticsearch';
import { LoggerFactory } from '../logging';
import { Plugin, PluginManifest } from './plugin';

View file

@ -17,8 +17,6 @@
* under the License.
*/
import { ElasticsearchServiceStart } from '../elasticsearch';
const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] });
jest.mock('../../../legacy/utils/package_json', () => ({ pkg: mockPackage }));
@ -32,7 +30,8 @@ import { BehaviorSubject, from } from 'rxjs';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { logger } from '../logging/__mocks__';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { PluginDiscoveryError } from './discovery';
import { Plugin } from './plugin';
import { PluginsService } from './plugins_service';
@ -44,7 +43,8 @@ let pluginsService: PluginsService;
let configService: ConfigService;
let env: Env;
let mockPluginSystem: jest.Mocked<PluginsSystem>;
let startDeps: { elasticsearch: ElasticsearchServiceStart };
const startDeps = { elasticsearch: elasticsearchServiceMock.createStartContract() };
const logger = loggingServiceMock.create();
beforeEach(() => {
mockPackage.raw = {
branch: 'feature-v1',
@ -57,7 +57,6 @@ beforeEach(() => {
};
env = Env.createDefault(getEnvOptions());
startDeps = { elasticsearch: { legacy: {} } as any };
configService = new ConfigService(
new BehaviorSubject<Config>(new ObjectToConfigAdapter({ plugins: { initialize: true } })),
@ -83,7 +82,7 @@ test('`start` throws if plugin has an invalid manifest', async () => {
[Error: Failed to initialize plugins:
Invalid JSON (invalid-manifest, path-1)]
`);
expect(logger.mockCollect().error).toMatchInlineSnapshot(`
expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(`
Array [
Array [
[Error: Invalid JSON (invalid-manifest, path-1)],
@ -104,7 +103,7 @@ test('`start` throws if plugin required Kibana version is incompatible with the
[Error: Failed to initialize plugins:
Incompatible version (incompatible-version, path-3)]
`);
expect(logger.mockCollect().error).toMatchInlineSnapshot(`
expect(loggingServiceMock.collect(logger).error).toMatchInlineSnapshot(`
Array [
Array [
[Error: Incompatible version (incompatible-version, path-3)],
@ -230,7 +229,7 @@ test('`start` properly detects plugins that should be disabled.', async () => {
expect(mockPluginSystem.startPlugins).toHaveBeenCalledTimes(1);
expect(mockPluginSystem.startPlugins).toHaveBeenCalledWith(startDeps);
expect(logger.mockCollect().info).toMatchInlineSnapshot(`
expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(`
Array [
Array [
"Plugin \\"explicitly-disabled-plugin\\" is disabled.",
@ -311,7 +310,7 @@ test('`start` properly invokes `discover` and ignores non-critical errors.', asy
{ env, logger, configService }
);
const logs = logger.mockCollect();
const logs = loggingServiceMock.collect(logger);
expect(logs.info).toHaveLength(0);
expect(logs.error).toHaveLength(0);
});

View file

@ -19,7 +19,8 @@
import { Observable } from 'rxjs';
import { filter, first, mergeMap, tap, toArray } from 'rxjs/operators';
import { CoreContext, CoreService } from '../../types';
import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { Logger } from '../logging';
import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { CoreContext } from '../../types';
import { CoreContext } from '../core_context';
const mockCreatePluginStartContext = jest.fn();
jest.mock('./plugin_context', () => ({
@ -27,11 +27,12 @@ jest.mock('./plugin_context', () => ({
import { BehaviorSubject } from 'rxjs';
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { ElasticsearchServiceStart } from '../elasticsearch';
import { logger } from '../logging/__mocks__';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { Plugin, PluginName } from './plugin';
import { PluginsSystem } from './plugins_system';
const logger = loggingServiceMock.create();
function createPlugin(
id: string,
{
@ -60,10 +61,9 @@ let pluginsSystem: PluginsSystem;
let configService: ConfigService;
let env: Env;
let coreContext: CoreContext;
let startDeps: { elasticsearch: ElasticsearchServiceStart };
const startDeps = { elasticsearch: elasticsearchServiceMock.createStartContract() };
beforeEach(() => {
env = Env.createDefault(getEnvOptions());
startDeps = { elasticsearch: { legacy: {} } as any };
configService = new ConfigService(
new BehaviorSubject<Config>(new ObjectToConfigAdapter({ plugins: { initialize: true } })),

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { CoreContext } from '../../types';
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { Plugin, PluginName } from './plugin';
import { createPluginStartContext } from './plugin_context';

View file

@ -17,14 +17,16 @@
* under the License.
*/
const mockLoggingService = { asLoggerFactory: jest.fn(), upgrade: jest.fn(), stop: jest.fn() };
import { loggingServiceMock } from '../logging/logging_service.mock';
const logger = loggingServiceMock.create();
jest.mock('../logging', () => ({
LoggingService: jest.fn(() => mockLoggingService),
LoggingService: jest.fn(() => logger),
}));
const mockConfigService = { atPath: jest.fn(), getConfig$: jest.fn() };
import { configServiceMock } from '../config/config_service.mock';
const configService = configServiceMock.create();
jest.mock('../config/config_service', () => ({
ConfigService: jest.fn(() => mockConfigService),
ConfigService: jest.fn(() => configService),
}));
const mockServer = { start: jest.fn(), stop: jest.fn() };
@ -33,31 +35,28 @@ jest.mock('../', () => ({ Server: jest.fn(() => mockServer) }));
import { BehaviorSubject } from 'rxjs';
import { filter, first } from 'rxjs/operators';
import { Root } from '.';
import { Config, Env } from '../config';
import { Env } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { logger } from '../logging/__mocks__';
const env = new Env('.', getEnvOptions());
const config$ = new BehaviorSubject({} as Config);
const config$ = configService.getConfig$();
let mockConsoleError: jest.SpyInstance;
beforeEach(() => {
jest.spyOn(global.process, 'exit').mockReturnValue(undefined as never);
mockConsoleError = jest.spyOn(console, 'error').mockReturnValue(undefined);
mockLoggingService.asLoggerFactory.mockReturnValue(logger);
mockConfigService.getConfig$.mockReturnValue(new BehaviorSubject({}));
mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ someValue: 'foo' }));
configService.atPath.mockReturnValue(new BehaviorSubject({ someValue: 'foo' }));
});
afterEach(() => {
jest.restoreAllMocks();
logger.asLoggerFactory.mockClear();
logger.stop.mockClear();
configService.getConfig$.mockClear();
mockLoggingService.upgrade.mockReset();
mockLoggingService.stop.mockReset();
mockLoggingService.asLoggerFactory.mockReset();
mockConfigService.atPath.mockReset();
mockConfigService.getConfig$.mockReset();
logger.upgrade.mockReset();
configService.atPath.mockReset();
mockServer.start.mockReset();
mockServer.stop.mockReset();
});
@ -65,31 +64,31 @@ afterEach(() => {
test('starts services on "start"', async () => {
const root = new Root(config$, env);
expect(mockLoggingService.upgrade).not.toHaveBeenCalled();
expect(logger.upgrade).not.toHaveBeenCalled();
expect(mockServer.start).not.toHaveBeenCalled();
await root.start();
expect(mockLoggingService.upgrade).toHaveBeenCalledTimes(1);
expect(mockLoggingService.upgrade).toHaveBeenLastCalledWith({ someValue: 'foo' });
expect(logger.upgrade).toHaveBeenCalledTimes(1);
expect(logger.upgrade).toHaveBeenLastCalledWith({ someValue: 'foo' });
expect(mockServer.start).toHaveBeenCalledTimes(1);
});
test('upgrades logging configuration after start', async () => {
const mockLoggingConfig$ = new BehaviorSubject({ someValue: 'foo' });
mockConfigService.atPath.mockReturnValue(mockLoggingConfig$);
configService.atPath.mockReturnValue(mockLoggingConfig$);
const root = new Root(config$, env);
await root.start();
expect(mockLoggingService.upgrade).toHaveBeenCalledTimes(1);
expect(mockLoggingService.upgrade).toHaveBeenLastCalledWith({ someValue: 'foo' });
mockLoggingService.upgrade.mockClear();
expect(logger.upgrade).toHaveBeenCalledTimes(1);
expect(logger.upgrade).toHaveBeenLastCalledWith({ someValue: 'foo' });
logger.upgrade.mockClear();
mockLoggingConfig$.next({ someValue: 'bar' });
expect(mockLoggingService.upgrade).toHaveBeenCalledTimes(1);
expect(mockLoggingService.upgrade).toHaveBeenLastCalledWith({ someValue: 'bar' });
expect(logger.upgrade).toHaveBeenCalledTimes(1);
expect(logger.upgrade).toHaveBeenLastCalledWith({ someValue: 'bar' });
});
test('stops services on "shutdown"', async () => {
@ -99,14 +98,14 @@ test('stops services on "shutdown"', async () => {
await root.start();
expect(mockOnShutdown).not.toHaveBeenCalled();
expect(mockLoggingService.stop).not.toHaveBeenCalled();
expect(logger.stop).not.toHaveBeenCalled();
expect(mockServer.stop).not.toHaveBeenCalled();
await root.shutdown();
expect(mockOnShutdown).toHaveBeenCalledTimes(1);
expect(mockOnShutdown).toHaveBeenCalledWith(undefined);
expect(mockLoggingService.stop).toHaveBeenCalledTimes(1);
expect(logger.stop).toHaveBeenCalledTimes(1);
expect(mockServer.stop).toHaveBeenCalledTimes(1);
});
@ -117,7 +116,7 @@ test('stops services on "shutdown" an calls `onShutdown` with error passed to `s
await root.start();
expect(mockOnShutdown).not.toHaveBeenCalled();
expect(mockLoggingService.stop).not.toHaveBeenCalled();
expect(logger.stop).not.toHaveBeenCalled();
expect(mockServer.stop).not.toHaveBeenCalled();
const someFatalError = new Error('some fatal error');
@ -125,7 +124,7 @@ test('stops services on "shutdown" an calls `onShutdown` with error passed to `s
expect(mockOnShutdown).toHaveBeenCalledTimes(1);
expect(mockOnShutdown).toHaveBeenCalledWith(someFatalError);
expect(mockLoggingService.stop).toHaveBeenCalledTimes(1);
expect(logger.stop).toHaveBeenCalledTimes(1);
expect(mockServer.stop).toHaveBeenCalledTimes(1);
});
@ -137,14 +136,14 @@ test('fails and stops services if server fails to start', async () => {
mockServer.start.mockRejectedValue(serverError);
expect(mockOnShutdown).not.toHaveBeenCalled();
expect(mockLoggingService.stop).not.toHaveBeenCalled();
expect(logger.stop).not.toHaveBeenCalled();
expect(mockServer.stop).not.toHaveBeenCalled();
await expect(root.start()).rejects.toThrowError('server failed');
expect(mockOnShutdown).toHaveBeenCalledTimes(1);
expect(mockOnShutdown).toHaveBeenCalledWith(serverError);
expect(mockLoggingService.stop).toHaveBeenCalledTimes(1);
expect(logger.stop).toHaveBeenCalledTimes(1);
expect(mockServer.stop).toHaveBeenCalledTimes(1);
});
@ -153,12 +152,12 @@ test('fails and stops services if initial logger upgrade fails', async () => {
const root = new Root(config$, env, mockOnShutdown);
const loggingUpgradeError = new Error('logging config upgrade failed');
mockLoggingService.upgrade.mockImplementation(() => {
logger.upgrade.mockImplementation(() => {
throw loggingUpgradeError;
});
expect(mockOnShutdown).not.toHaveBeenCalled();
expect(mockLoggingService.stop).not.toHaveBeenCalled();
expect(logger.stop).not.toHaveBeenCalled();
expect(mockServer.start).not.toHaveBeenCalled();
await expect(root.start()).rejects.toThrowError('logging config upgrade failed');
@ -166,30 +165,30 @@ test('fails and stops services if initial logger upgrade fails', async () => {
expect(mockOnShutdown).toHaveBeenCalledTimes(1);
expect(mockOnShutdown).toHaveBeenCalledWith(loggingUpgradeError);
expect(mockServer.start).not.toHaveBeenCalled();
expect(mockLoggingService.stop).toHaveBeenCalledTimes(1);
expect(logger.stop).toHaveBeenCalledTimes(1);
expect(mockConsoleError.mock.calls).toMatchSnapshot();
});
test('stops services if consequent logger upgrade fails', async () => {
const onShutdown = new BehaviorSubject<string | null>(null);
const mockOnShutdown = jest.fn<any, any>(() => {
const mockOnShutdown = jest.fn(() => {
onShutdown.next('completed');
onShutdown.complete();
});
const mockLoggingConfig$ = new BehaviorSubject({ someValue: 'foo' });
mockConfigService.atPath.mockReturnValue(mockLoggingConfig$);
configService.atPath.mockReturnValue(mockLoggingConfig$);
const root = new Root(config$, env, mockOnShutdown);
await root.start();
expect(mockOnShutdown).not.toHaveBeenCalled();
expect(mockLoggingService.stop).not.toHaveBeenCalled();
expect(logger.stop).not.toHaveBeenCalled();
expect(mockServer.stop).not.toHaveBeenCalled();
const loggingUpgradeError = new Error('logging config consequent upgrade failed');
mockLoggingService.upgrade.mockImplementation(() => {
logger.upgrade.mockImplementation(() => {
throw loggingUpgradeError;
});
mockLoggingConfig$.next({ someValue: 'bar' });
@ -204,7 +203,7 @@ test('stops services if consequent logger upgrade fails', async () => {
expect(mockOnShutdown).toHaveBeenCalledTimes(1);
expect(mockOnShutdown).toHaveBeenCalledWith(loggingUpgradeError);
expect(mockLoggingService.stop).toHaveBeenCalledTimes(1);
expect(logger.stop).toHaveBeenCalledTimes(1);
expect(mockServer.stop).toHaveBeenCalledTimes(1);
expect(mockConsoleError.mock.calls).toMatchSnapshot();

View file

@ -17,5 +17,11 @@
* under the License.
*/
export { CoreContext } from './core_context';
export { CoreService } from './core_service';
/**
* Use * syntax so that these exports do not break when internal
* types are stripped.
*
* No imports in this directory can import from ./server or ./public
* or else builds will not work correctly for both NodeJS and Webpack.
*/
export * from './core_service';

View file

@ -54,8 +54,11 @@ export const TranspileTypescriptTask = {
const projects = [
typesProjectRepo.tsConfigPath,
typesProjectBuild.tsConfigPath,
// Browser needs to be compiled before server code so that any shared code
// is compiled to the lowest common denominator (server's CommonJS format)
// which can be supported by both environments.
browserProject.tsConfigPath,
defaultProject.tsConfigPath,
browserProject.tsConfigPath
];
// compile each typescript config file

View file

@ -84,7 +84,6 @@ fi
### "install" node into this shell
###
export PATH="$nodeBin:$PATH"
hash -r
###
### downloading yarn
@ -102,7 +101,6 @@ yarn config set yarn-offline-mirror "$cacheDir/yarn-offline-cache"
###
yarnGlobalDir="$(yarn global bin)"
export PATH="$PATH:$yarnGlobalDir"
hash -r
###
### use the chromedriver cache if it exists

View file

@ -53,7 +53,6 @@ export default {
'<rootDir>/src/dev/jest/setup/babel_polyfill.js',
'<rootDir>/src/dev/jest/setup/polyfills.js',
'<rootDir>/src/dev/jest/setup/enzyme.js',
'<rootDir>/src/dev/jest/setup/throw_on_console_error.js',
],
coverageDirectory: '<rootDir>/target/jest-coverage',
coverageReporters: [

View file

@ -21,18 +21,23 @@ import { inspect } from 'util';
const FAIL_TAG = Symbol('fail error');
export function createFailError(reason, exitCode = 1) {
const error = new Error(reason);
error.exitCode = exitCode;
error[FAIL_TAG] = true;
return error;
interface FailError extends Error {
exitCode: number;
[FAIL_TAG]: true;
}
export function isFailError(error) {
export function createFailError(reason: string, exitCode = 1): FailError {
return Object.assign(new Error(reason), {
exitCode,
[FAIL_TAG]: true as true,
});
}
export function isFailError(error: any): error is FailError {
return Boolean(error && error[FAIL_TAG]);
}
export function combineErrors(errors) {
export function combineErrors(errors: Array<Error | FailError>) {
if (errors.length === 1) {
return errors[0];
}

View file

@ -21,8 +21,19 @@ import { relative } from 'path';
import getopts from 'getopts';
export function getFlags(argv) {
return getopts(argv, {
export interface Flags {
verbose: boolean;
quiet: boolean;
silent: boolean;
debug: boolean;
help: boolean;
_: string[];
[key: string]: undefined | boolean | string | string[];
}
export function getFlags(argv: string[]): Flags {
const { verbose, quiet, silent, debug, help, _, ...others } = getopts(argv, {
alias: {
v: 'verbose',
},
@ -32,14 +43,23 @@ export function getFlags(argv) {
silent: false,
debug: false,
help: false,
}
},
});
return {
verbose,
quiet,
silent,
debug,
help,
_,
...others,
};
}
export function getHelp() {
return (
`
node ${relative(process.cwd(), process.argv[1], '.js')}
return `
node ${relative(process.cwd(), process.argv[1])}
Runs a dev task
@ -50,6 +70,5 @@ export function getHelp() {
--silent Don't log anything
--help Show this message
`
);
`;
}

View file

@ -1,25 +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 { ToolingLog } from '@kbn/dev-utils';
export function createFailError(msg: string, exitCode?: number): Error;
export function run(
body: (args: { flags: Record<string, any>; log: ToolingLog }) => void
): Promise<void>;

View file

@ -17,11 +17,11 @@
* under the License.
*/
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { pickLevelFromFlags, ToolingLog } from '@kbn/dev-utils';
import { isFailError } from './fail';
import { getFlags, getHelp } from './flags';
import { Flags, getFlags, getHelp } from './flags';
export async function run(body) {
export async function run(body: (args: { log: ToolingLog; flags: Flags }) => Promise<void> | void) {
const flags = getFlags(process.argv.slice(2));
if (flags.help) {
@ -31,7 +31,7 @@ export async function run(body) {
const log = new ToolingLog({
level: pickLevelFromFlags(flags),
writeTo: process.stdout
writeTo: process.stdout,
});
try {

View file

@ -49,6 +49,16 @@ run(
);
}
if (typeof path === 'boolean' || typeof includeConfig === 'boolean') {
throw createFailError(
`${chalk.white.bgRed(' I18N ERROR ')} --path and --include-config require a value`
);
}
if (typeof fix !== 'boolean') {
throw createFailError(`${chalk.white.bgRed(' I18N ERROR ')} --fix can't have a value`);
}
const config = await mergeConfigs(includeConfig);
const defaultMessages = await extractDefaultMessages({ path, config });

View file

@ -39,6 +39,12 @@ run(
);
}
if (typeof path === 'boolean' || typeof includeConfig === 'boolean') {
throw createFailError(
`${chalk.white.bgRed(' I18N ERROR ')} --path and --include-config require a value`
);
}
const config = await mergeConfigs(includeConfig);
const defaultMessages = await extractDefaultMessages({ path, config });

View file

@ -47,9 +47,30 @@ run(
);
}
if (Array.isArray(target)) {
if (typeof target === 'boolean' || Array.isArray(target)) {
throw createFailError(
`${chalk.white.bgRed(' I18N ERROR ')} --target should be specified only once.`
`${chalk.white.bgRed(
' I18N ERROR '
)} --target should be specified only once and must have a value.`
);
}
if (typeof path === 'boolean' || typeof includeConfig === 'boolean') {
throw createFailError(
`${chalk.white.bgRed(' I18N ERROR ')} --path and --include-config require a value`
);
}
if (
typeof ignoreIncompatible !== 'boolean' ||
typeof ignoreUnused !== 'boolean' ||
typeof ignoreMissing !== 'boolean' ||
typeof dryRun !== 'boolean'
) {
throw createFailError(
`${chalk.white.bgRed(
' I18N ERROR '
)} --ignore-incompatible, --ignore-unused, --ignore-missing, and --dry-run can't have values`
);
}

View file

@ -103,7 +103,7 @@ export default function (kibana) {
}
const config = server.config();
const legacyEsConfig = await server.core.elasticsearch.legacy.config$.pipe(first()).toPromise();
const legacyEsConfig = await server.newPlatform.start.core.elasticsearch.legacy.config$.pipe(first()).toPromise();
const proxyConfigCollection = new ProxyConfigCollection(options.proxyConfig);
const proxyPathFilters = options.proxyFilter.map(str => new RegExp(str));

View file

@ -36,9 +36,9 @@ export default function (kibana) {
// value from all observables here to be able to synchronously return and create
// cluster clients afterwards.
const [esConfig, adminCluster, dataCluster] = await combineLatest(
server.core.elasticsearch.legacy.config$,
server.core.elasticsearch.adminClient$,
server.core.elasticsearch.dataClient$
server.newPlatform.start.core.elasticsearch.legacy.config$,
server.newPlatform.start.core.elasticsearch.adminClient$,
server.newPlatform.start.core.elasticsearch.dataClient$
).pipe(
first(),
map(([config, adminClusterClient, dataClusterClient]) => [
@ -80,7 +80,7 @@ export default function (kibana) {
// We fill all the missing properties in the `clientConfig` using the default
// Elasticsearch config so that we don't depend on default values set and
// controlled by underlying Elasticsearch JS client.
const cluster = new Cluster(server.core.elasticsearch.createClient(name, {
const cluster = new Cluster(server.newPlatform.start.core.elasticsearch.createClient(name, {
...esConfig,
...clientConfig,
}));

View file

@ -94,6 +94,7 @@ exports[`renders ControlsTab 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
id="selectControlType"
labelType="label"
>
<EuiSelect
aria-label="Select control type"
@ -127,6 +128,7 @@ exports[`renders ControlsTab 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
id="addControl"
labelType="label"
>
<EuiButton
aria-label="Add control"

View file

@ -28,6 +28,7 @@ exports[`renders dynamic options should display disabled dynamic options with to
}
id="multiselect-0"
key="multiselect"
labelType="label"
>
<EuiSwitch
checked={true}
@ -55,6 +56,7 @@ exports[`renders dynamic options should display disabled dynamic options with to
}
id="dynamicOptions-0"
key="dynamicOptions"
labelType="label"
>
<EuiSwitch
checked={true}
@ -90,6 +92,7 @@ exports[`renders dynamic options should display disabled dynamic options with to
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}
@ -132,6 +135,7 @@ exports[`renders dynamic options should display dynamic options for string field
}
id="multiselect-0"
key="multiselect"
labelType="label"
>
<EuiSwitch
checked={true}
@ -159,6 +163,7 @@ exports[`renders dynamic options should display dynamic options for string field
}
id="dynamicOptions-0"
key="dynamicOptions"
labelType="label"
>
<EuiSwitch
checked={true}
@ -205,6 +210,7 @@ exports[`renders dynamic options should display size field when dynamic options
}
id="multiselect-0"
key="multiselect"
labelType="label"
>
<EuiSwitch
checked={true}
@ -232,6 +238,7 @@ exports[`renders dynamic options should display size field when dynamic options
}
id="dynamicOptions-0"
key="dynamicOptions"
labelType="label"
>
<EuiSwitch
checked={false}
@ -267,6 +274,7 @@ exports[`renders dynamic options should display size field when dynamic options
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}
@ -316,6 +324,7 @@ exports[`renders should display chaining input when parents are provided 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiSelect
compressed={false}
@ -354,6 +363,7 @@ exports[`renders should display chaining input when parents are provided 1`] = `
}
id="multiselect-0"
key="multiselect"
labelType="label"
>
<EuiSwitch
checked={true}
@ -381,6 +391,7 @@ exports[`renders should display chaining input when parents are provided 1`] = `
}
id="dynamicOptions-0"
key="dynamicOptions"
labelType="label"
>
<EuiSwitch
checked={false}
@ -416,6 +427,7 @@ exports[`renders should display chaining input when parents are provided 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}

View file

@ -7,6 +7,7 @@ exports[`renders OptionsTab 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
id="updateFiltersOnChange"
labelType="label"
>
<EuiSwitch
checked={false}
@ -26,6 +27,7 @@ exports[`renders OptionsTab 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
id="useTimeFilter"
labelType="label"
>
<EuiSwitch
checked={false}
@ -45,6 +47,7 @@ exports[`renders OptionsTab 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
id="pinFilters"
labelType="label"
>
<EuiSwitch
data-test-subj="inputControlEditorPinFiltersCheckbox"

View file

@ -27,6 +27,7 @@ exports[`renders RangeControlEditor 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}
@ -49,6 +50,7 @@ exports[`renders RangeControlEditor 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}

View file

@ -8,6 +8,7 @@ exports[`renders disabled control with tooltip 1`] = `
hasEmptyLabelSpace={false}
id="controlId"
label="test control"
labelType="label"
>
<EuiToolTip
content="I am disabled for testing purposes"
@ -30,6 +31,7 @@ exports[`renders enabled control 1`] = `
hasEmptyLabelSpace={false}
id="controlId"
label="test control"
labelType="label"
>
<div>
My Control

View file

@ -13,12 +13,13 @@ exports[`disabled 1`] = `
fullWidth={false}
levels={Array []}
max={100}
min={1}
min={0}
showInput={false}
showLabels={false}
showRange={false}
showTicks={false}
showValue={false}
step={1}
/>
</FormRow>
`;
@ -37,6 +38,7 @@ exports[`renders RangeControl 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
labelType="label"
>
<EuiFlexGroup
alignItems="center"

View file

@ -30,9 +30,7 @@ exports[`renders DashboardCloneModal 1`] = `
/>
</p>
</EuiText>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiFieldText
autoFocus={true}
compressed={false}

View file

@ -18,6 +18,7 @@ exports[`renders DashboardSaveModal 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiTextArea
compressed={true}
@ -46,6 +47,7 @@ exports[`renders DashboardSaveModal 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiSwitch
checked={true}

View file

@ -45,9 +45,7 @@ exports[`apmUiEnabled 1`] = `
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiFlexGroup
alignItems="stretch"
className="homeAddData__flexGroup"
@ -84,7 +82,6 @@ exports[`apmUiEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="apmApp"
/>
}
@ -120,7 +117,6 @@ exports[`apmUiEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="loggingApp"
/>
}
@ -156,7 +152,6 @@ exports[`apmUiEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="monitoringApp"
/>
}
@ -192,7 +187,6 @@ exports[`apmUiEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="securityApp"
/>
}
@ -203,10 +197,7 @@ exports[`apmUiEnabled 1`] = `
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiHorizontalRule />
<EuiFlexGrid
columns={2}
gutterSize="l"
@ -343,9 +334,7 @@ exports[`isNewKibanaInstance 1`] = `
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiFlexGroup
alignItems="stretch"
className="homeAddData__flexGroup"
@ -382,7 +371,6 @@ exports[`isNewKibanaInstance 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="loggingApp"
/>
}
@ -418,7 +406,6 @@ exports[`isNewKibanaInstance 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="monitoringApp"
/>
}
@ -454,7 +441,6 @@ exports[`isNewKibanaInstance 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="securityApp"
/>
}
@ -465,10 +451,7 @@ exports[`isNewKibanaInstance 1`] = `
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiHorizontalRule />
<EuiFlexGrid
columns={2}
gutterSize="l"
@ -605,9 +588,7 @@ exports[`mlEnabled 1`] = `
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiFlexGroup
alignItems="stretch"
className="homeAddData__flexGroup"
@ -644,7 +625,6 @@ exports[`mlEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="apmApp"
/>
}
@ -680,7 +660,6 @@ exports[`mlEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="loggingApp"
/>
}
@ -716,7 +695,6 @@ exports[`mlEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="monitoringApp"
/>
}
@ -752,7 +730,6 @@ exports[`mlEnabled 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="securityApp"
/>
}
@ -763,10 +740,7 @@ exports[`mlEnabled 1`] = `
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiHorizontalRule />
<EuiFlexGrid
columns={3}
gutterSize="l"
@ -944,9 +918,7 @@ exports[`render 1`] = `
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiFlexGroup
alignItems="stretch"
className="homeAddData__flexGroup"
@ -983,7 +955,6 @@ exports[`render 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="loggingApp"
/>
}
@ -1019,7 +990,6 @@ exports[`render 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="monitoringApp"
/>
}
@ -1055,7 +1025,6 @@ exports[`render 1`] = `
icon={
<EuiIcon
className="homAddData__icon"
size="m"
type="securityApp"
/>
}
@ -1066,10 +1035,7 @@ exports[`render 1`] = `
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiHorizontalRule />
<EuiFlexGrid
columns={2}
gutterSize="l"

View file

@ -86,7 +86,6 @@ exports[`render 1`] = `
>
<EuiIcon
color="subdued"
size="m"
type="dot"
/>
</EuiText>

View file

@ -2,10 +2,7 @@
exports[`render 1`] = `
<div>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiHorizontalRule />
<EuiFlexGroup
alignItems="center"
component="div"

View file

@ -42,9 +42,7 @@ exports[`props exportedFieldsUrl 1`] = `
text="this is a great tutorial about..."
/>
<div>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiButton
color="primary"
fill={false}

View file

@ -553,7 +553,6 @@ exports[`bulkCreate should display success message when bulkCreate is successful
>
<EuiIcon
className="euiStepNumber__icon"
size="m"
title="complete"
type="check"
>

View file

@ -12,15 +12,11 @@ exports[`isCloudEnabled is false should not render instruction toggle when ON_PR
description="tutorial used to drive jest tests"
title="jest test tutorial"
/>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<div
className="eui-textCenter"
/>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiPanel
grow={true}
hasShadow={false}
@ -67,9 +63,7 @@ exports[`isCloudEnabled is false should render ON_PREM instructions with instruc
iconType="logoApache"
title="jest test tutorial"
/>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<div
className="eui-textCenter"
>
@ -109,9 +103,7 @@ exports[`isCloudEnabled is false should render ON_PREM instructions with instruc
</EuiFlexItem>
</EuiFlexGroup>
</div>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiPanel
grow={true}
hasShadow={false}
@ -158,15 +150,11 @@ exports[`should render ELASTIC_CLOUD instructions when isCloudEnabled is true 1`
iconType="logoApache"
title="jest test tutorial"
/>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<div
className="eui-textCenter"
/>
<EuiSpacer
size="l"
/>
<EuiSpacer />
<EuiPanel
grow={true}
hasShadow={false}

View file

@ -80,6 +80,7 @@ exports[`Header should mark the input as invalid 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
compressed={false}
@ -196,6 +197,7 @@ exports[`Header should render normally 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
compressed={false}

View file

@ -11,12 +11,22 @@ exports[`IndicesList should change pages 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -25,12 +35,22 @@ exports[`IndicesList should change pages 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -138,12 +158,22 @@ exports[`IndicesList should change per page 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -261,6 +291,11 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
<span>
@ -272,6 +307,11 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -280,12 +320,22 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -393,12 +443,22 @@ exports[`IndicesList should render normally 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -407,12 +467,22 @@ exports[`IndicesList should render normally 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -520,12 +590,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -534,12 +614,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -548,12 +638,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -562,12 +662,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -576,12 +686,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -590,12 +710,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -604,12 +734,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -618,12 +758,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -632,12 +782,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
kibana
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>
@ -646,12 +806,22 @@ exports[`IndicesList updating props should render all new indices 1`] = `
>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
>
es
</EuiTableRowCell>
<EuiTableRowCell
align="left"
mobileOptions={
Object {
"show": true,
}
}
textOnly={true}
/>
</EuiTableRow>

View file

@ -11,7 +11,6 @@ exports[`StatusMessage should render with exact matches 1`] = `
component="span"
>
<EuiIcon
size="m"
type="check"
/>
<span>

View file

@ -56,6 +56,7 @@ exports[`TimeField should render a loading state 1`] = `
</EuiFlexItem>
</EuiFlexGroup>
}
labelType="label"
>
<EuiSelect
compressed={false}
@ -143,6 +144,7 @@ exports[`TimeField should render a selected time field 1`] = `
</EuiFlexItem>
</EuiFlexGroup>
}
labelType="label"
>
<EuiSelect
compressed={false}
@ -232,6 +234,7 @@ exports[`TimeField should render normally 1`] = `
</EuiFlexItem>
</EuiFlexGroup>
}
labelType="label"
>
<EuiSelect
compressed={false}

View file

@ -9,9 +9,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = `
ownFocus={false}
size="s"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -202,9 +200,7 @@ exports[`Flyout should render import step 1`] = `
ownFocus={false}
size="s"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -231,6 +227,7 @@ exports[`Flyout should render import step 1`] = `
values={Object {}}
/>
}
labelType="label"
>
<EuiFilePicker
compressed={false}
@ -248,6 +245,7 @@ exports[`Flyout should render import step 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiSwitch
checked={true}

View file

@ -9,9 +9,7 @@ exports[`Relationships should render dashboards normally 1`] = `
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -72,7 +70,7 @@ exports[`Relationships should render dashboards normally 1`] = `
Array [
Object {
"render": [Function],
"width": 24,
"width": "24px",
},
Object {
"field": "title",
@ -121,9 +119,7 @@ exports[`Relationships should render errors 1`] = `
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -172,9 +168,7 @@ exports[`Relationships should render index patterns normally 1`] = `
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -197,9 +191,129 @@ exports[`Relationships should render index patterns normally 1`] = `
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiLoadingKibana
size="xl"
/>
<EuiDescriptionList
align="left"
compressed={false}
textStyle="normal"
type="row"
>
<EuiDescriptionListTitle
style={
Object {
"marginBottom": "1rem",
}
}
>
<EuiCallOut
color="warning"
size="m"
title={
<FormattedMessage
defaultMessage="Warning"
id="kbn.management.objects.objectsTable.relationships.warningTitle"
values={Object {}}
/>
}
>
<p />
</EuiCallOut>
</EuiDescriptionListTitle>
<EuiInMemoryTable
columns={
Array [
Object {
"render": [Function],
"width": "24px",
},
Object {
"field": "title",
"name": "Title",
"render": [Function],
},
Object {
"actions": Array [
Object {
"description": "View this saved object within Kibana",
"icon": "eye",
"name": "In app",
"onClick": [Function],
},
],
"name": "Actions",
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
"id": "1",
},
]
}
pagination={true}
responsive={true}
sorting={false}
/>
<EuiDescriptionListTitle
style={
Object {
"marginBottom": "1rem",
}
}
>
<EuiCallOut
color="warning"
size="m"
title={
<FormattedMessage
defaultMessage="Warning"
id="kbn.management.objects.objectsTable.relationships.warningTitle"
values={Object {}}
/>
}
>
<p />
</EuiCallOut>
</EuiDescriptionListTitle>
<EuiInMemoryTable
columns={
Array [
Object {
"render": [Function],
"width": "24px",
},
Object {
"field": "title",
"name": "Title",
"render": [Function],
},
Object {
"actions": Array [
Object {
"description": "View this saved object within Kibana",
"icon": "eye",
"name": "In app",
"onClick": [Function],
},
],
"name": "Actions",
},
]
}
executeQueryOptions={Object {}}
items={
Array [
Object {
"id": "2",
},
]
}
pagination={true}
responsive={true}
sorting={false}
/>
</EuiDescriptionList>
</EuiFlyoutBody>
</EuiFlyout>
`;
@ -213,9 +327,7 @@ exports[`Relationships should render searches normally 1`] = `
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -276,7 +388,7 @@ exports[`Relationships should render searches normally 1`] = `
Array [
Object {
"render": [Function],
"width": 24,
"width": "24px",
},
Object {
"field": "title",
@ -340,7 +452,7 @@ exports[`Relationships should render searches normally 1`] = `
Array [
Object {
"render": [Function],
"width": 24,
"width": "24px",
},
Object {
"field": "title",
@ -386,9 +498,7 @@ exports[`Relationships should render visualizations normally 1`] = `
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={false}
>
<EuiFlyoutHeader>
<EuiTitle
size="m"
textTransform="none"
@ -449,7 +559,7 @@ exports[`Relationships should render visualizations normally 1`] = `
Array [
Object {
"render": [Function],
"width": 24,
"width": "24px",
},
Object {
"field": "title",

View file

@ -227,7 +227,7 @@ class RelationshipsUI extends Component {
items={list}
columns={[
{
width: 24,
width: '24px',
render: () => (
<EuiToolTip
position="top"

View file

@ -86,6 +86,7 @@ exports[`Field for array setting should render as read only with help text if ov
}
isInvalid={false}
label="array:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="array test setting"
@ -171,6 +172,7 @@ exports[`Field for array setting should render custom setting icon if it is cust
helpText={null}
isInvalid={false}
label="array:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="array test setting"
@ -245,6 +247,7 @@ exports[`Field for array setting should render default value if there is no user
helpText={null}
isInvalid={false}
label="array:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="array test setting"
@ -361,6 +364,7 @@ exports[`Field for array setting should render user value if there is user value
}
isInvalid={false}
label="array:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="array test setting"
@ -469,6 +473,7 @@ exports[`Field for boolean setting should render as read only with help text if
}
isInvalid={false}
label="boolean:test:setting"
labelType="label"
>
<EuiSwitch
aria-label="boolean test setting"
@ -558,6 +563,7 @@ exports[`Field for boolean setting should render custom setting icon if it is cu
helpText={null}
isInvalid={false}
label="boolean:test:setting"
labelType="label"
>
<EuiSwitch
aria-label="boolean test setting"
@ -636,6 +642,7 @@ exports[`Field for boolean setting should render default value if there is no us
helpText={null}
isInvalid={false}
label="boolean:test:setting"
labelType="label"
>
<EuiSwitch
aria-label="boolean test setting"
@ -756,6 +763,7 @@ exports[`Field for boolean setting should render user value if there is user val
}
isInvalid={false}
label="boolean:test:setting"
labelType="label"
>
<EuiSwitch
aria-label="boolean test setting"
@ -868,6 +876,7 @@ exports[`Field for image setting should render as read only with help text if ov
}
isInvalid={false}
label="image:test:setting"
labelType="label"
>
<EuiImage
allowFullScreen={true}
@ -950,6 +959,7 @@ exports[`Field for image setting should render custom setting icon if it is cust
helpText={null}
isInvalid={false}
label="image:test:setting"
labelType="label"
>
<EuiFilePicker
accept=".jpg,.jpeg,.png"
@ -1022,6 +1032,7 @@ exports[`Field for image setting should render default value if there is no user
helpText={null}
isInvalid={false}
label="image:test:setting"
labelType="label"
>
<EuiFilePicker
accept=".jpg,.jpeg,.png"
@ -1151,6 +1162,7 @@ exports[`Field for image setting should render user value if there is user value
}
isInvalid={false}
label="image:test:setting"
labelType="label"
>
<EuiImage
allowFullScreen={true}
@ -1260,6 +1272,7 @@ exports[`Field for json setting should render as read only with help text if ove
}
isInvalid={false}
label="json:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-json:test:setting"
@ -1283,6 +1296,7 @@ exports[`Field for json setting should render as read only with help text if ove
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="{\\"hello\\": \\"world\\"}"
width="100%"
@ -1361,6 +1375,7 @@ exports[`Field for json setting should render custom setting icon if it is custo
helpText={null}
isInvalid={false}
label="json:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-json:test:setting"
@ -1384,6 +1399,7 @@ exports[`Field for json setting should render custom setting icon if it is custo
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="{\\"foo\\": \\"bar\\"}"
width="100%"
@ -1497,6 +1513,7 @@ exports[`Field for json setting should render default value if there is no user
}
isInvalid={false}
label="json:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-json:test:setting"
@ -1520,6 +1537,7 @@ exports[`Field for json setting should render default value if there is no user
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="{\\"foo\\": \\"bar\\"}"
width="100%"
@ -1633,6 +1651,7 @@ exports[`Field for json setting should render user value if there is user value
}
isInvalid={false}
label="json:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-json:test:setting"
@ -1656,6 +1675,7 @@ exports[`Field for json setting should render user value if there is user value
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="{\\"hello\\": \\"world\\"}"
width="100%"
@ -1757,6 +1777,7 @@ exports[`Field for markdown setting should render as read only with help text if
}
isInvalid={false}
label="markdown:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-markdown:test:setting"
@ -1780,6 +1801,7 @@ exports[`Field for markdown setting should render as read only with help text if
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="**bold**"
width="100%"
@ -1858,6 +1880,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c
helpText={null}
isInvalid={false}
label="markdown:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-markdown:test:setting"
@ -1881,6 +1904,7 @@ exports[`Field for markdown setting should render custom setting icon if it is c
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value=""
width="100%"
@ -1948,6 +1972,7 @@ exports[`Field for markdown setting should render default value if there is no u
helpText={null}
isInvalid={false}
label="markdown:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-markdown:test:setting"
@ -1971,6 +1996,7 @@ exports[`Field for markdown setting should render default value if there is no u
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value=""
width="100%"
@ -2080,6 +2106,7 @@ exports[`Field for markdown setting should render user value if there is user va
}
isInvalid={false}
label="markdown:test:setting"
labelType="label"
>
<div
data-test-subj="advancedSetting-editField-markdown:test:setting"
@ -2103,6 +2130,7 @@ exports[`Field for markdown setting should render user value if there is user va
"tabSize": 2,
}
}
showGutter={false}
theme="textmate"
value="**bold**"
width="100%"
@ -2204,6 +2232,7 @@ exports[`Field for number setting should render as read only with help text if o
}
isInvalid={false}
label="number:test:setting"
labelType="label"
>
<EuiFieldNumber
aria-label="number test setting"
@ -2289,6 +2318,7 @@ exports[`Field for number setting should render custom setting icon if it is cus
helpText={null}
isInvalid={false}
label="number:test:setting"
labelType="label"
>
<EuiFieldNumber
aria-label="number test setting"
@ -2363,6 +2393,7 @@ exports[`Field for number setting should render default value if there is no use
helpText={null}
isInvalid={false}
label="number:test:setting"
labelType="label"
>
<EuiFieldNumber
aria-label="number test setting"
@ -2479,6 +2510,7 @@ exports[`Field for number setting should render user value if there is user valu
}
isInvalid={false}
label="number:test:setting"
labelType="label"
>
<EuiFieldNumber
aria-label="number test setting"
@ -2587,6 +2619,7 @@ exports[`Field for select setting should render as read only with help text if o
}
isInvalid={false}
label="select:test:setting"
labelType="label"
>
<EuiSelect
aria-label="select test setting"
@ -2689,6 +2722,7 @@ exports[`Field for select setting should render custom setting icon if it is cus
helpText={null}
isInvalid={false}
label="select:test:setting"
labelType="label"
>
<EuiSelect
aria-label="select test setting"
@ -2780,6 +2814,7 @@ exports[`Field for select setting should render default value if there is no use
helpText={null}
isInvalid={false}
label="select:test:setting"
labelType="label"
>
<EuiSelect
aria-label="select test setting"
@ -2913,6 +2948,7 @@ exports[`Field for select setting should render user value if there is user valu
}
isInvalid={false}
label="select:test:setting"
labelType="label"
>
<EuiSelect
aria-label="select test setting"
@ -3038,6 +3074,7 @@ exports[`Field for string setting should render as read only with help text if o
}
isInvalid={false}
label="string:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="string test setting"
@ -3123,6 +3160,7 @@ exports[`Field for string setting should render custom setting icon if it is cus
helpText={null}
isInvalid={false}
label="string:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="string test setting"
@ -3197,6 +3235,7 @@ exports[`Field for string setting should render default value if there is no use
helpText={null}
isInvalid={false}
label="string:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="string test setting"
@ -3313,6 +3352,7 @@ exports[`Field for string setting should render user value if there is user valu
}
isInvalid={false}
label="string:test:setting"
labelType="label"
>
<EuiFieldText
aria-label="string test setting"

View file

@ -397,7 +397,7 @@ class FieldUI extends PureComponent {
editorProps={{
$blockScrolling: Infinity
}}
showGutter={false}
/>
</div>
);

View file

@ -62,8 +62,6 @@ class TableListViewUi extends React.Component {
showLimitError: false,
filter: this.props.initialFilter,
selectedIds: [],
sortField: 'title',
sortDirection: 'asc',
page: 0,
perPage: 20,
};
@ -92,11 +90,12 @@ class TableListViewUi extends React.Component {
// We need this check to handle the case where search results come back in a different
// order than they were sent out. Only load results for the most recent search.
// Also, in case filter is empty, items are being pre-sorted alphabetically.
if (filter === this.state.filter) {
this.setState({
hasInitialFetchReturned: true,
isFetchingItems: false,
items: response.hits,
items: (!filter ? _.sortBy(response.hits, 'title') : response.hits),
totalItems: response.total,
showLimitError: response.total > this.props.listingLimit,
});
@ -336,14 +335,6 @@ class TableListViewUi extends React.Component {
onClick: this.props.editItem
}];
const sorting = {};
if (this.state.sortField) {
sorting.sort = {
field: this.state.sortField,
direction: this.state.sortDirection,
};
}
const search = {
onChange: this.setFilter.bind(this),
toolsLeft: this.renderToolsLeft(),
@ -382,7 +373,7 @@ class TableListViewUi extends React.Component {
message={noItemsMessage}
selection={selection}
search={search}
sorting={sorting}
sorting={true}
hasActions={!this.state.hideWriteControls}
data-test-subj="itemsInMemTable"
/>

View file

@ -18,6 +18,6 @@
*/
import dateMath from '@elastic/datemath';
export const GTE_INTERVAL_RE = new RegExp(`^>=([\\d\\.]*\\s*(${dateMath.units.join('|')}))$`);
export const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$');
export const GTE_INTERVAL_RE = new RegExp(`^>=([\\d\\.]+\\s*(${dateMath.units.join('|')}))$`);
export const INTERVAL_STRING_RE = new RegExp(`^([\\d\\.]+)\\s*(${dateMath.units.join('|')})$`);

View file

@ -0,0 +1,94 @@
/*
* 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 { GTE_INTERVAL_RE, INTERVAL_STRING_RE } from './interval_regexp';
describe('interval_regexp', () => {
describe('GTE_INTERVAL_RE', () => {
test('returns true for">=12h"', () => {
const value = GTE_INTERVAL_RE.test('>=12h');
expect(value).toBeTruthy();
});
test('returns true for ">=1y"', () => {
const value = GTE_INTERVAL_RE.test('>=12h');
expect(value).toBeTruthy();
});
test('returns true for ">=25m"', () => {
const value = GTE_INTERVAL_RE.test('>=12h');
expect(value).toBeTruthy();
});
test('returns false "auto"', () => {
const value = GTE_INTERVAL_RE.test('auto');
expect(value).toBeFalsy();
});
test('returns false "wrongInput"', () => {
const value = GTE_INTERVAL_RE.test('wrongInput');
expect(value).toBeFalsy();
});
test('returns false "d"', () => {
const value = GTE_INTERVAL_RE.test('d');
expect(value).toBeFalsy();
});
test('returns false "y"', () => {
const value = GTE_INTERVAL_RE.test('y');
expect(value).toBeFalsy();
});
});
describe('INTERVAL_STRING_RE', () => {
test('returns true for "8d"', () => {
const value = INTERVAL_STRING_RE.test('8d');
expect(value).toBeTruthy();
});
test('returns true for "1y"', () => {
const value = INTERVAL_STRING_RE.test('1y');
expect(value).toBeTruthy();
});
test('returns true for "6M"', () => {
const value = INTERVAL_STRING_RE.test('6M');
expect(value).toBeTruthy();
});
test('returns false "auto"', () => {
const value = INTERVAL_STRING_RE.test('auto');
expect(value).toBeFalsy();
});
test('returns false "wrongInput"', () => {
const value = INTERVAL_STRING_RE.test('wrongInput');
expect(value).toBeFalsy();
});
test('returns false for">=21h"', () => {
const value = INTERVAL_STRING_RE.test('>=21h');
expect(value).toBeFalsy();
});
});
});

View file

@ -64,10 +64,12 @@ class PercentilesUi extends Component {
<EuiFlexItem grow={false}>
<EuiFieldNumber
aria-label={intl.formatMessage({ id: 'tsvb.percentile.percentileAriaLabel', defaultMessage: 'Percentile' })}
placeholder={intl.formatMessage({ id: 'tsvb.percentile.percentilePlaceholder', defaultMessage: 'Percentile' })}
placeholder={0}
max={100}
min={0}
step={1}
onChange={this.handleTextChange(model, 'value')}
value={Number(model.value)}
value={model.value === '' ? '' : Number(model.value)}
/>
</EuiFlexItem>
);

View file

@ -117,6 +117,7 @@ export const IndexPattern = props => {
disabled={props.disabled}
onChange={handleTextChange(intervalName, 'auto')}
value={model[intervalName]}
placeholder={'auto'}
/>
</EuiFormRow>
</EuiFlexItem>

View file

@ -16,32 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
import { relativeOptions } from '../../../../../ui/public/timepicker/relative_options';
import _ from 'lodash';
import moment from 'moment';
import { convertIntervalIntoUnit } from './get_interval';
import { i18n } from '@kbn/i18n';
const unitLookup = {
s: i18n.translate('tsvb.axisLabelOptions.secondsLabel', { defaultMessage: 'seconds' }),
m: i18n.translate('tsvb.axisLabelOptions.minutesLabel', { defaultMessage: 'minutes' }),
h: i18n.translate('tsvb.axisLabelOptions.hoursLabel', { defaultMessage: 'hours' }),
d: i18n.translate('tsvb.axisLabelOptions.daysLabel', { defaultMessage: 'days' }),
w: i18n.translate('tsvb.axisLabelOptions.weeksLabel', { defaultMessage: 'weeks' }),
M: i18n.translate('tsvb.axisLabelOptions.monthsLabel', { defaultMessage: 'months' }),
y: i18n.translate('tsvb.axisLabelOptions.yearsLabel', { defaultMessage: 'years' })
};
export function getAxisLabelString(interval) {
const units = _.pluck(_.clone(relativeOptions).reverse(), 'value')
.filter(s => /^[smhdwMy]$/.test(s));
const duration = moment.duration(interval, 'ms');
for (let i = 0; i < units.length; i++) {
const as = duration.as(units[i]);
if (Math.abs(as) > 1) {
const unitValue = Math.round(Math.abs(as));
const unitString = unitLookup[units[i]];
return i18n.translate('tsvb.axisLabelOptions.axisLabel',
{ defaultMessage: 'per {unitValue} {unitString}', values: { unitValue, unitString } });
}
const convertedValue = convertIntervalIntoUnit(interval);
if (convertedValue) {
return i18n.translate('tsvb.axisLabelOptions.axisLabel',
{
defaultMessage: 'per {unitValue} {unitString}',
values: {
unitValue: convertedValue.unitValue,
unitString: convertedValue.unitString,
},
});
}
}

View file

@ -0,0 +1,75 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { pluck, get, clone, isString } from 'lodash';
import { relativeOptions } from '../../../../../ui/public/timepicker/relative_options';
import { GTE_INTERVAL_RE, INTERVAL_STRING_RE } from '../../../common/interval_regexp';
export const unitLookup = {
s: i18n.translate('tsvb.getInterval.secondsLabel', { defaultMessage: 'seconds' }),
m: i18n.translate('tsvb.getInterval.minutesLabel', { defaultMessage: 'minutes' }),
h: i18n.translate('tsvb.getInterval.hoursLabel', { defaultMessage: 'hours' }),
d: i18n.translate('tsvb.getInterval.daysLabel', { defaultMessage: 'days' }),
w: i18n.translate('tsvb.getInterval.weeksLabel', { defaultMessage: 'weeks' }),
M: i18n.translate('tsvb.getInterval.monthsLabel', { defaultMessage: 'months' }),
y: i18n.translate('tsvb.getInterval.yearsLabel', { defaultMessage: 'years' })
};
export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) => {
const units = pluck(clone(relativeOptions).reverse(), 'value')
.filter(s => /^[smhdwMy]$/.test(s));
const duration = moment.duration(interval, 'ms');
for (let i = 0; i < units.length; i++) {
const as = duration.as(units[i]);
if (Math.abs(as) > 1) {
return {
unitValue: Math.round(Math.abs(as)),
unitString: hasTranslateUnitString ? unitLookup[units[i]] : units[i]
};
}
}
};
export const isGteInterval = (interval) => GTE_INTERVAL_RE.test(interval);
export const isIntervalValid = (interval) => {
return isString(interval) &&
(interval === 'auto' || INTERVAL_STRING_RE.test(interval) || isGteInterval(interval));
};
export const getInterval = (visData, model) => {
let series;
if (model && model.type === 'table') {
series = get(visData, `series[0].series`, []);
} else {
series = get(visData, `${model.id}.series`, []);
}
return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);
};

View file

@ -23,7 +23,7 @@ import FieldSelect from '../aggs/field_select';
import SeriesEditor from '../series_editor';
import { IndexPattern } from '../index_pattern';
import createTextHandler from '../lib/create_text_handler';
import createSelectHandler from '../lib/create_select_handler';
import { get } from 'lodash';
import uuid from 'uuid';
import YesNo from '../yes_no';
import {
@ -64,11 +64,21 @@ class TablePanelConfig extends Component {
this.setState({ selectedTab });
}
handlePivotChange = (selectedOption) => {
const { fields, model } = this.props;
const pivotId = get(selectedOption, '[0].value', null);
const field = fields[model.index_pattern].find(field => field.name === pivotId);
const pivotType = field.type || model.pivot_type;
this.props.onChange({
pivot_id: pivotId,
pivot_type: pivotType
});
};
render() {
const { selectedTab } = this.state;
const defaults = { drilldown_url: '', filter: '', pivot_label: '', pivot_rows: 10 };
const defaults = { drilldown_url: '', filter: '', pivot_label: '', pivot_rows: 10, pivot_type: '' };
const model = { ...defaults, ...this.props.model };
const handleSelectChange = createSelectHandler(this.props.onChange);
const handleTextChange = createTextHandler(this.props.onChange);
const htmlId = htmlIdGenerator();
let view;
@ -100,7 +110,7 @@ class TablePanelConfig extends Component {
fields={this.props.fields}
value={model.pivot_id}
indexPattern={model.index_pattern}
onChange={handleSelectChange('pivot_id')}
onChange={this.handlePivotChange}
fullWidth
/>
</EuiFormRow>

View file

@ -1,196 +1,173 @@
/*
* 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 PropTypes from 'prop-types';
import React, { Component } from 'react';
import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
import VisEditorVisualization from './vis_editor_visualization';
import Visualization from './visualization';
import VisPicker from './vis_picker';
import PanelConfig from './panel_config';
import brushHandler from '../lib/create_brush_handler';
import { extractIndexPatterns } from '../lib/extract_index_patterns';
import { fetchFields } from '../lib/fetch_fields';
import chrome from 'ui/chrome';
class VisEditor extends Component {
constructor(props) {
super(props);
const { vis } = props;
this.appState = vis.API.getAppState();
this.state = {
model: props.visParams,
dirty: false,
autoApply: true,
visFields: {},
};
this.onBrush = brushHandler(props.vis.API.timeFilter);
this.handleUiState = this.handleUiState.bind(this, props.vis);
this.getConfig = this.getConfig.bind(this);
this.visDataSubject = new Rx.Subject();
this.visData$ = this.visDataSubject.asObservable().pipe(share());
}
getConfig(...args) {
return this.props.config.get(...args);
}
handleUiState(vis, ...args) {
vis.uiStateVal(...args);
}
fetchIndexPatternFields = async () => {
const { params } = this.props.vis;
const { visFields } = this.state;
const indexPatterns = extractIndexPatterns(params, visFields);
const fields = await fetchFields(indexPatterns);
this.setState((previousState) => {
return {
visFields: {
...previousState.visFields,
...fields,
}
};
});
}
setDefaultIndexPattern = async () => {
const savedObjectsClient = chrome.getSavedObjectsClient();
const indexPattern = await savedObjectsClient.get('index-pattern', this.getConfig('defaultIndex'));
this.handleChange({
default_index_pattern: indexPattern.attributes.title
});
}
handleChange = async (partialModel) => {
const nextModel = { ...this.state.model, ...partialModel };
this.props.vis.params = nextModel;
if (this.state.autoApply) {
this.props.vis.updateState();
}
this.setState({
model: nextModel,
dirty: !this.state.autoApply,
});
this.fetchIndexPatternFields();
}
handleCommit = () => {
this.props.vis.updateState();
this.setState({ dirty: false });
}
handleAutoApplyToggle = (event) => {
this.setState({ autoApply: event.target.checked });
}
onDataChange = ({ visData }) => {
this.visDataSubject.next(visData);
}
render() {
if (!this.props.isEditorMode) {
if (!this.props.visParams || !this.props.visData) {
return null;
}
return (
<Visualization
dateFormat={this.props.config.get('dateFormat')}
onBrush={this.onBrush}
onUiState={this.handleUiState}
uiState={this.props.vis.getUiState()}
fields={this.state.visFields}
model={this.props.visParams}
visData={this.props.visData}
getConfig={this.getConfig}
/>
);
}
const { model } = this.state;
if (model) {
return (
<div className="tvbEditor">
<div className="tvbEditor--hideForReporting">
<VisPicker model={model} onChange={this.handleChange} />
</div>
<VisEditorVisualization
dirty={this.state.dirty}
autoApply={this.state.autoApply}
model={model}
appState={this.appState}
savedObj={this.props.savedObj}
timeRange={this.props.timeRange}
onUiState={this.handleUiState}
uiState={this.props.vis.getUiState()}
onBrush={this.onBrush}
onCommit={this.handleCommit}
onToggleAutoApply={this.handleAutoApplyToggle}
onChange={this.handleChange}
title={this.props.vis.title}
description={this.props.vis.description}
dateFormat={this.props.config.get('dateFormat')}
onDataChange={this.onDataChange}
/>
<div className="tvbEditor--hideForReporting">
<PanelConfig
fields={this.state.visFields}
model={model}
visData$={this.visData$}
dateFormat={this.props.config.get('dateFormat')}
onChange={this.handleChange}
getConfig={this.getConfig}
/>
</div>
</div>
);
}
return null;
}
async componentDidMount() {
await this.setDefaultIndexPattern();
await this.fetchIndexPatternFields();
this.props.renderComplete();
}
componentDidUpdate() {
this.props.renderComplete();
}
}
VisEditor.defaultProps = {
visData: {}
};
VisEditor.propTypes = {
vis: PropTypes.object,
visData: PropTypes.object,
renderComplete: PropTypes.func,
config: PropTypes.object,
isEditorMode: PropTypes.bool,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
};
export default VisEditor;
/*
* 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 PropTypes from 'prop-types';
import React, { Component } from 'react';
import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
import VisEditorVisualization from './vis_editor_visualization';
import Visualization from './visualization';
import VisPicker from './vis_picker';
import PanelConfig from './panel_config';
import brushHandler from '../lib/create_brush_handler';
import { fetchIndexPatternFields } from '../lib/fetch_fields';
class VisEditor extends Component {
constructor(props) {
super(props);
const { vis } = props;
this.appState = vis.API.getAppState();
this.state = {
model: props.visParams,
dirty: false,
autoApply: true,
visFields: props.visFields
};
this.onBrush = brushHandler(props.vis.API.timeFilter);
this.visDataSubject = new Rx.Subject();
this.visData$ = this.visDataSubject.asObservable().pipe(share());
}
get uiState() {
return this.props.vis.getUiState();
}
getConfig = (...args) => {
return this.props.config.get(...args);
};
handleUiState = (field, value) => {
this.props.vis.uiStateVal(field, value);
};
handleChange = async (partialModel) => {
const nextModel = { ...this.state.model, ...partialModel };
this.props.vis.params = nextModel;
if (this.state.autoApply) {
this.props.vis.updateState();
}
this.setState({
model: nextModel,
dirty: !this.state.autoApply
});
const { params, fields } = this.props.vis;
fetchIndexPatternFields(params, fields).then(visFields => {
this.setState({ visFields });
});
};
handleCommit = () => {
this.props.vis.updateState();
this.setState({ dirty: false });
};
handleAutoApplyToggle = (event) => {
this.setState({ autoApply: event.target.checked });
};
onDataChange = ({ visData }) => {
this.visDataSubject.next(visData);
};
render() {
if (!this.props.isEditorMode) {
if (!this.props.visParams || !this.props.visData) {
return null;
}
return (
<Visualization
dateFormat={this.props.config.get('dateFormat')}
onBrush={this.onBrush}
onUiState={this.handleUiState}
uiState={this.uiState}
model={this.props.visParams}
visData={this.props.visData}
getConfig={this.getConfig}
/>
);
}
const { model } = this.state;
if (model) {
return (
<div className="tvbEditor">
<div className="tvbEditor--hideForReporting">
<VisPicker model={model} onChange={this.handleChange} />
</div>
<VisEditorVisualization
dirty={this.state.dirty}
autoApply={this.state.autoApply}
model={model}
appState={this.appState}
savedObj={this.props.savedObj}
timeRange={this.props.timeRange}
onUiState={this.handleUiState}
uiState={this.uiState}
onBrush={this.onBrush}
onCommit={this.handleCommit}
onToggleAutoApply={this.handleAutoApplyToggle}
onChange={this.handleChange}
title={this.props.vis.title}
description={this.props.vis.description}
dateFormat={this.props.config.get('dateFormat')}
onDataChange={this.onDataChange}
/>
<div className="tvbEditor--hideForReporting">
<PanelConfig
fields={this.state.visFields}
model={model}
visData$={this.visData$}
dateFormat={this.props.config.get('dateFormat')}
onChange={this.handleChange}
getConfig={this.getConfig}
/>
</div>
</div>
);
}
return null;
}
componentDidMount() {
this.props.renderComplete();
}
componentDidUpdate() {
this.props.renderComplete();
}
}
VisEditor.defaultProps = {
visData: {}
};
VisEditor.propTypes = {
vis: PropTypes.object,
visData: PropTypes.object,
visFields: PropTypes.object,
renderComplete: PropTypes.func,
config: PropTypes.object,
isEditorMode: PropTypes.bool,
savedObj: PropTypes.object,
timeRange: PropTypes.object,
};
export default VisEditor;

View file

@ -16,12 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { get } from 'lodash';
import { keyCodes, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui';
import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { getInterval, convertIntervalIntoUnit, isIntervalValid, isGteInterval } from './lib/get_interval';
const MIN_CHART_HEIGHT = 250;
@ -30,7 +31,8 @@ class VisEditorVisualization extends Component {
super(props);
this.state = {
height: MIN_CHART_HEIGHT,
dragging: false
dragging: false,
panelInterval: 0,
};
this.handleMouseUp = this.handleMouseUp.bind(this);
@ -53,7 +55,7 @@ class VisEditorVisualization extends Component {
this.handleMouseMove = (event) => {
if (this.state.dragging) {
this.setState((prevState) => ({
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY)
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY),
}));
}
};
@ -67,16 +69,16 @@ class VisEditorVisualization extends Component {
if (this._handler) {
this._handler.destroy();
}
if(this._subscription) {
if (this._subscription) {
this._subscription.unsubscribe();
}
}
onUpdate = () => {
this._handler.update({
timeRange: this.props.timeRange
timeRange: this.props.timeRange,
});
}
};
_loadVisualization() {
getVisualizeLoader().then(loader => {
@ -94,6 +96,7 @@ class VisEditorVisualization extends Component {
});
this._subscription = this._handler.data$.subscribe((data) => {
this.setPanelInterval(data.visData);
this.props.onDataChange(data);
});
@ -103,6 +106,14 @@ class VisEditorVisualization extends Component {
});
}
setPanelInterval(visData) {
const panelInterval = getInterval(visData, this.props.model);
if (this.state.panelInterval !== panelInterval) {
this.setState({ panelInterval });
}
}
componentDidUpdate() {
if (!this._handler) {
this._handlerUpdateHasAlreadyBeenTriggered = true;
@ -115,6 +126,7 @@ class VisEditorVisualization extends Component {
componentDidMount() {
this._loadVisualization();
}
/**
* Resize the chart height when pressing up/down while the drag handle
* for resizing has the focus.
@ -128,12 +140,40 @@ class VisEditorVisualization extends Component {
this.setState((prevState) => {
const newHeight = prevState.height + (keyCode === keyCodes.UP ? -15 : 15);
return {
height: Math.max(MIN_CHART_HEIGHT, newHeight)
height: Math.max(MIN_CHART_HEIGHT, newHeight),
};
});
}
}
hasShowPanelIntervalValue() {
const type = get(this.props, 'model.type', '');
return [
'metric',
'top_n',
'gauge',
'markdown',
'table',
].includes(type);
}
getFormattedPanelInterval() {
const interval = get(this.props, 'model.interval') || 'auto';
const isValid = isIntervalValid(interval);
const shouldShowActualInterval = interval === 'auto' || isGteInterval(interval);
if (shouldShowActualInterval || !isValid) {
const autoInterval = convertIntervalIntoUnit(this.state.panelInterval, false);
if (autoInterval) {
return `${autoInterval.unitValue}${autoInterval.unitString}`;
}
} else {
return interval;
}
}
render() {
const { dirty, autoApply } = this.props;
const style = { height: this.state.height };
@ -141,6 +181,8 @@ class VisEditorVisualization extends Component {
style.userSelect = 'none';
}
const panelInterval = this.hasShowPanelIntervalValue() && this.getFormattedPanelInterval();
let applyMessage = (<FormattedMessage
id="tsvb.visEditorVisualization.changesSuccessfullyAppliedMessage"
defaultMessage="The latest changes have been applied."
@ -171,6 +213,22 @@ class VisEditorVisualization extends Component {
/>
</EuiFlexItem>
{panelInterval &&
<EuiFlexItem grow={false}>
<EuiText color="default" size="xs">
<p>
<FormattedMessage
id="tsvb.visEditorVisualization.panelInterval"
defaultMessage="Interval: {panelInterval}"
values={{
panelInterval: panelInterval,
}}
/>
</p>
</EuiText>
</EuiFlexItem>
}
<EuiFlexItem grow={false}>
<EuiText color={dirty ? 'default' : 'subdued'} size="xs">
<p>
@ -180,14 +238,14 @@ class VisEditorVisualization extends Component {
</EuiFlexItem>
{!autoApply &&
<EuiFlexItem grow={false}>
<EuiButton iconType="play" fill size="s" onClick={this.props.onCommit} disabled={!dirty}>
<FormattedMessage
id="tsvb.visEditorVisualization.applyChangesLabel"
defaultMessage="Apply changes"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="play" fill size="s" onClick={this.props.onCommit} disabled={!dirty}>
<FormattedMessage
id="tsvb.visEditorVisualization.applyChangesLabel"
defaultMessage="Apply changes"
/>
</EuiButton>
</EuiFlexItem>
}
</EuiFlexGroup>
);
@ -213,10 +271,10 @@ class VisEditorVisualization extends Component {
onKeyDown={this.onSizeHandleKeyDown}
aria-label={this.props.intl.formatMessage({
id: 'tsvb.colorRules.adjustChartSizeAriaLabel',
defaultMessage: 'Press up/down to adjust the chart size'
defaultMessage: 'Press up/down to adjust the chart size',
})}
>
<i className="fa fa-ellipsis-h" />
<i className="fa fa-ellipsis-h"/>
</button>
</div>
</div>

View file

@ -20,6 +20,7 @@
import _ from 'lodash';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { fieldFormats } from 'ui/registry/field_formats';
import tickFormatter from '../../lib/tick_formatter';
import calculateLabel from '../../../../common/calculate_label';
import { isSortable } from './is_sortable';
@ -27,6 +28,8 @@ import { EuiToolTip, EuiIcon } from '@elastic/eui';
import replaceVars from '../../lib/replace_vars';
import { FormattedMessage } from '@kbn/i18n/react';
const DateFormat = fieldFormats.getType('date');
function getColor(rules, colorKey, value) {
let color;
if (rules) {
@ -51,13 +54,12 @@ class TableVis extends Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.dateFormatter = new DateFormat({}, this.props.getConfig);
}
renderRow(row) {
renderRow = row => {
const { model } = this.props;
const rowId = row.key;
let rowDisplay = rowId;
let rowDisplay = model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key;
if (model.drilldown_url) {
const url = replaceVars(model.drilldown_url, {}, { key: row.key });
rowDisplay = (<a href={url}>{rowDisplay}</a>);
@ -78,14 +80,14 @@ class TableVis extends Component {
}
const style = { color: getColor(column.color_rules, 'text', item.last) };
return (
<td key={`${rowId}-${item.id}`} data-test-subj="tvbTableVis__value" className="eui-textRight" style={style}>
<td key={`${row.key}-${item.id}`} data-test-subj="tvbTableVis__value" className="eui-textRight" style={style}>
<span>{ value }</span>
{trend}
</td>
);
});
return (
<tr key={rowId}>
<tr key={row.key}>
<td>{rowDisplay}</td>
{columns}
</tr>

View file

@ -27,6 +27,7 @@ import _ from 'lodash';
import Timeseries from '../../../visualizations/components/timeseries';
import replaceVars from '../../lib/replace_vars';
import { getAxisLabelString } from '../../lib/get_axis_label_string';
import { getInterval } from '../../lib/get_interval';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';
function hasSeparateAxis(row) {
@ -35,20 +36,10 @@ function hasSeparateAxis(row) {
class TimeseriesVisualization extends Component {
constructor(props) {
super(props);
}
getInterval = () => {
const { visData, model } = this.props;
const series = _.get(visData, `${model.id}.series`, []);
return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);
return getInterval(visData, model);
}
xaxisFormatter = (val) => {

Some files were not shown because too many files have changed in this diff Show more