mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Merge branch 'master' of github.com:elastic/kibana into pr/43529
This commit is contained in:
commit
f72dd9850e
256 changed files with 12573 additions and 6452 deletions
|
@ -44,6 +44,7 @@ tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \
|
|||
x-pack/legacy/plugins/*/node_modules \
|
||||
x-pack/legacy/plugins/reporting/.chromium \
|
||||
test/plugin_functional/plugins/*/node_modules \
|
||||
examples/*/node_modules \
|
||||
.es \
|
||||
.chromedriver \
|
||||
.geckodriver;
|
||||
|
|
|
@ -65,6 +65,8 @@ connects to this Kibana instance.
|
|||
`elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List
|
||||
of Kibana client-side headers to send to Elasticsearch. To send *no* client-side
|
||||
headers, set this value to [] (an empty list).
|
||||
Removing the `authorization` header from being whitelisted means that you cannot
|
||||
use <<basic-authentication, basic authentication>> in Kibana.
|
||||
|
||||
`elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait
|
||||
for responses from the back end or Elasticsearch. This value must be a positive
|
||||
|
|
8
examples/README.md
Normal file
8
examples/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Example plugins
|
||||
|
||||
This folder contains example plugins. To run the plugins in this folder, use the `--run-examples` flag, via
|
||||
|
||||
```
|
||||
yarn start --run-examples
|
||||
```
|
||||
|
8
examples/demo_search/README.md
Normal file
8
examples/demo_search/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Demo search strategy
|
||||
|
||||
This example registers a custom search strategy that simply takes a name string in the request and returns the
|
||||
string `Hello {name}`
|
||||
|
||||
To see the demo search strategy in action, navigate to the `Search explorer` app.
|
||||
|
||||
To run these examples, use the command `yarn start --run-examples`.
|
|
@ -17,10 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public';
|
||||
|
||||
export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY';
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../scripts/kbn.js",
|
||||
"kbn": "node ../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && tsc"
|
||||
},
|
||||
"devDependencies": {
|
|
@ -22,8 +22,8 @@ import {
|
|||
ISearchContext,
|
||||
SYNC_SEARCH_STRATEGY,
|
||||
ISearchGeneric,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import { TSearchStrategyProvider, ISearchStrategy } from '../../../../../src/plugins/data/public';
|
||||
} from '../../../src/plugins/data/public';
|
||||
import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public';
|
||||
|
||||
import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common';
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public';
|
||||
import { Plugin, CoreSetup, PluginInitializerContext } from '../../../../../src/core/public';
|
||||
import { DataPublicPluginSetup } from '../../../src/plugins/data/public';
|
||||
import { Plugin, CoreSetup, PluginInitializerContext } from '../../../src/core/public';
|
||||
import { DEMO_SEARCH_STRATEGY } from '../common';
|
||||
import { demoClientSearchStrategyProvider } from './demo_search_strategy';
|
||||
import { IDemoRequest, IDemoResponse } from '../common';
|
||||
|
@ -36,7 +36,7 @@ interface DemoDataSearchSetupDependencies {
|
|||
* If the caller does not pass in the right `request` shape, typescript will
|
||||
* complain. The caller will also get a typed response.
|
||||
*/
|
||||
declare module '../../../../../src/plugins/data/public' {
|
||||
declare module '../../../src/plugins/data/public' {
|
||||
export interface IRequestTypesMap {
|
||||
[DEMO_SEARCH_STRATEGY]: IDemoRequest;
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { TSearchStrategyProvider } from 'src/plugins/data/server';
|
||||
import { TSearchStrategyProvider } from '../../../src/plugins/data/server';
|
||||
import { DEMO_SEARCH_STRATEGY } from '../common';
|
||||
|
||||
export const demoSearchStrategyProvider: TSearchStrategyProvider<typeof DEMO_SEARCH_STRATEGY> = () => {
|
|
@ -35,7 +35,7 @@ interface IDemoSearchExplorerDeps {
|
|||
* If the caller does not pass in the right `request` shape, typescript will
|
||||
* complain. The caller will also get a typed response.
|
||||
*/
|
||||
declare module '../../../../../src/plugins/data/server' {
|
||||
declare module '../../../src/plugins/data/server' {
|
||||
export interface IRequestTypesMap {
|
||||
[DEMO_SEARCH_STRATEGY]: IDemoRequest;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
|
@ -10,7 +10,7 @@
|
|||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../../../typings/**/*"
|
||||
"../../typings/**/*"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
8
examples/search_explorer/README.md
Normal file
8
examples/search_explorer/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Search explorer
|
||||
|
||||
This example search explorer app shows how to use different search strategies in order to retrieve data.
|
||||
|
||||
One demo uses the built in elasticsearch search strategy, and runs a search against data in elasticsearch. The
|
||||
other demo uses the custom demo search strategy, a custom search strategy registerd inside the [demo_search plugin](../demo_search).
|
||||
|
||||
To run this example, use the command `yarn start --run-examples`.
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../scripts/kbn.js",
|
||||
"kbn": "node ../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && tsc"
|
||||
},
|
||||
"devDependencies": {
|
|
@ -28,7 +28,7 @@ import {
|
|||
EuiSideNav,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { AppMountContext, AppMountParameters } from '../../../../../src/core/public';
|
||||
import { AppMountContext, AppMountParameters } from '../../../src/core/public';
|
||||
import { EsSearchTest } from './es_strategy';
|
||||
import { Page } from './page';
|
||||
import { DemoStrategy } from './demo_strategy';
|
|
@ -25,7 +25,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFieldText,
|
||||
} from '@elastic/eui';
|
||||
import { ISearchGeneric } from '../../../../../src/plugins/data/public';
|
||||
import { ISearchGeneric } from '../../../src/plugins/data/public';
|
||||
import { DoSearch } from './do_search';
|
||||
import { GuideSection } from './guide_section';
|
||||
|
|
@ -21,10 +21,7 @@ import React from 'react';
|
|||
import { EuiButton, EuiCodeBlock, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui';
|
||||
import { EuiProgress } from '@elastic/eui';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
IKibanaSearchResponse,
|
||||
IKibanaSearchRequest,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../../src/plugins/data/public';
|
||||
|
||||
interface Props {
|
||||
request: IKibanaSearchRequest;
|
|
@ -29,19 +29,19 @@ import {
|
|||
ISearchGeneric,
|
||||
IEsSearchResponse,
|
||||
IEsSearchRequest,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
} from '../../../src/plugins/data/public';
|
||||
import { DoSearch } from './do_search';
|
||||
import { GuideSection } from './guide_section';
|
||||
|
||||
// @ts-ignore
|
||||
import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_service';
|
||||
import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_service';
|
||||
// @ts-ignore
|
||||
import serverStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_strategy';
|
||||
import serverStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_strategy';
|
||||
|
||||
// @ts-ignore
|
||||
import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_service';
|
||||
import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_service';
|
||||
// @ts-ignore
|
||||
import publicStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_strategy';
|
||||
import publicStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_strategy';
|
||||
|
||||
interface Props {
|
||||
search: ISearchGeneric;
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { Plugin, CoreSetup } from 'kibana/public';
|
||||
import { ISearchAppMountContext } from '../../../../../src/plugins/data/public';
|
||||
import { ISearchAppMountContext } from '../../../src/plugins/data/public';
|
||||
|
||||
declare module 'kibana/public' {
|
||||
interface AppMountContext {
|
|
@ -20,22 +20,22 @@ import React from 'react';
|
|||
import { GuideSection } from './guide_section';
|
||||
|
||||
// @ts-ignore
|
||||
import publicSetupContract from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_setup';
|
||||
import publicSetupContract from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_setup';
|
||||
// @ts-ignore
|
||||
import publicSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_strategy';
|
||||
import publicSearchStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_strategy';
|
||||
// @ts-ignore
|
||||
import publicSearch from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search';
|
||||
import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search';
|
||||
// @ts-ignore
|
||||
import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/search_service';
|
||||
import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service';
|
||||
|
||||
// @ts-ignore
|
||||
import serverSetupContract from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_setup';
|
||||
import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup';
|
||||
// @ts-ignore
|
||||
import serverSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_strategy';
|
||||
import serverSearchStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_strategy';
|
||||
// @ts-ignore
|
||||
import serverSearch from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search';
|
||||
import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search';
|
||||
// @ts-ignore
|
||||
import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/search_service';
|
||||
import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service';
|
||||
|
||||
export const SearchApiPage = () => (
|
||||
<GuideSection
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
|
@ -9,7 +9,7 @@
|
|||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../../../typings/**/*",
|
||||
"../../typings/**/*",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
|
@ -97,6 +97,7 @@
|
|||
"packages/*",
|
||||
"x-pack",
|
||||
"x-pack/legacy/plugins/*",
|
||||
"examples/*",
|
||||
"test/plugin_functional/plugins/*",
|
||||
"test/interpreter_functional/plugins/*"
|
||||
],
|
||||
|
|
|
@ -1,95 +1,95 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { resolve } = require('path');
|
||||
|
||||
const del = require('del');
|
||||
const supportsColor = require('supports-color');
|
||||
const { run, withProcRunner } = require('@kbn/dev-utils');
|
||||
|
||||
const ROOT_DIR = resolve(__dirname, '..');
|
||||
const BUILD_DIR = resolve(ROOT_DIR, 'target');
|
||||
|
||||
const padRight = (width, str) =>
|
||||
str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`;
|
||||
|
||||
run(
|
||||
async ({ log, flags }) => {
|
||||
await withProcRunner(log, async proc => {
|
||||
log.info('Deleting old output');
|
||||
await del(BUILD_DIR);
|
||||
|
||||
const cwd = ROOT_DIR;
|
||||
const env = { ...process.env };
|
||||
if (supportsColor.stdout) {
|
||||
env.FORCE_COLOR = 'true';
|
||||
}
|
||||
|
||||
log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`);
|
||||
await Promise.all([
|
||||
...['web', 'node'].map(subTask =>
|
||||
proc.run(padRight(10, `babel:${subTask}`), {
|
||||
cmd: 'babel',
|
||||
args: [
|
||||
'src',
|
||||
'--config-file',
|
||||
require.resolve('../babel.config.js'),
|
||||
'--out-dir',
|
||||
resolve(BUILD_DIR, subTask),
|
||||
'--extensions',
|
||||
'.ts,.js,.tsx',
|
||||
...(flags.watch ? ['--watch'] : ['--quiet']),
|
||||
...(flags['source-maps'] ? ['--source-map', 'inline'] : []),
|
||||
],
|
||||
wait: true,
|
||||
env: {
|
||||
...env,
|
||||
BABEL_ENV: subTask,
|
||||
},
|
||||
cwd,
|
||||
})
|
||||
),
|
||||
|
||||
proc.run(padRight(10, 'tsc'), {
|
||||
cmd: 'tsc',
|
||||
args: [
|
||||
'--emitDeclarationOnly',
|
||||
...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []),
|
||||
...(flags['source-maps'] ? ['--declarationMap', 'true'] : []),
|
||||
],
|
||||
wait: true,
|
||||
env,
|
||||
cwd,
|
||||
}),
|
||||
]);
|
||||
|
||||
log.success('Complete');
|
||||
});
|
||||
},
|
||||
{
|
||||
description: 'Simple build tool for @kbn/analytics package',
|
||||
flags: {
|
||||
boolean: ['watch', 'source-maps'],
|
||||
help: `
|
||||
--watch Run in watch mode
|
||||
--source-maps Include sourcemaps
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { resolve } = require('path');
|
||||
|
||||
const del = require('del');
|
||||
const supportsColor = require('supports-color');
|
||||
const { run, withProcRunner } = require('@kbn/dev-utils');
|
||||
|
||||
const ROOT_DIR = resolve(__dirname, '..');
|
||||
const BUILD_DIR = resolve(ROOT_DIR, 'target');
|
||||
|
||||
const padRight = (width, str) =>
|
||||
str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`;
|
||||
|
||||
run(
|
||||
async ({ log, flags }) => {
|
||||
await withProcRunner(log, async proc => {
|
||||
log.info('Deleting old output');
|
||||
await del(BUILD_DIR);
|
||||
|
||||
const cwd = ROOT_DIR;
|
||||
const env = { ...process.env };
|
||||
if (supportsColor.stdout) {
|
||||
env.FORCE_COLOR = 'true';
|
||||
}
|
||||
|
||||
log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`);
|
||||
await Promise.all([
|
||||
...['web', 'node'].map(subTask =>
|
||||
proc.run(padRight(10, `babel:${subTask}`), {
|
||||
cmd: 'babel',
|
||||
args: [
|
||||
'src',
|
||||
'--config-file',
|
||||
require.resolve('../babel.config.js'),
|
||||
'--out-dir',
|
||||
resolve(BUILD_DIR, subTask),
|
||||
'--extensions',
|
||||
'.ts,.js,.tsx',
|
||||
...(flags.watch ? ['--watch'] : ['--quiet']),
|
||||
...(flags['source-maps'] ? ['--source-maps', 'inline'] : []),
|
||||
],
|
||||
wait: true,
|
||||
env: {
|
||||
...env,
|
||||
BABEL_ENV: subTask,
|
||||
},
|
||||
cwd,
|
||||
})
|
||||
),
|
||||
|
||||
proc.run(padRight(10, 'tsc'), {
|
||||
cmd: 'tsc',
|
||||
args: [
|
||||
'--emitDeclarationOnly',
|
||||
...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []),
|
||||
...(flags['source-maps'] ? ['--declarationMap', 'true'] : []),
|
||||
],
|
||||
wait: true,
|
||||
env,
|
||||
cwd,
|
||||
}),
|
||||
]);
|
||||
|
||||
log.success('Complete');
|
||||
});
|
||||
},
|
||||
{
|
||||
description: 'Simple build tool for @kbn/analytics package',
|
||||
flags: {
|
||||
boolean: ['watch', 'source-maps'],
|
||||
help: `
|
||||
--watch Run in watch mode
|
||||
--source-maps Include sourcemaps
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -55,7 +55,7 @@ run(
|
|||
'--extensions',
|
||||
'.ts,.js,.tsx',
|
||||
...(flags.watch ? ['--watch'] : ['--quiet']),
|
||||
...(flags['source-maps'] ? ['--source-map', 'inline'] : []),
|
||||
...(flags['source-maps'] ? ['--source-maps', 'inline'] : []),
|
||||
],
|
||||
wait: true,
|
||||
env: {
|
||||
|
|
1
packages/kbn-pm/dist/index.js
vendored
1
packages/kbn-pm/dist/index.js
vendored
|
@ -23185,6 +23185,7 @@ function getProjectPaths(rootPath, options = {}) {
|
|||
|
||||
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*'));
|
||||
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*'));
|
||||
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*'));
|
||||
|
||||
if (!ossOnly) {
|
||||
projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack'));
|
||||
|
|
|
@ -44,6 +44,7 @@ export function getProjectPaths(rootPath: string, options: IProjectPathOptions =
|
|||
// correct and the expect behavior.
|
||||
projectPaths.push(resolve(rootPath, 'test/plugin_functional/plugins/*'));
|
||||
projectPaths.push(resolve(rootPath, 'test/interpreter_functional/plugins/*'));
|
||||
projectPaths.push(resolve(rootPath, 'examples/*'));
|
||||
|
||||
if (!ossOnly) {
|
||||
projectPaths.push(resolve(rootPath, 'x-pack'));
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
'x-pack/package.json',
|
||||
'x-pack/legacy/plugins/*/package.json',
|
||||
'packages/*/package.json',
|
||||
'examples/*/package.json',
|
||||
'test/plugin_functional/plugins/*/package.json',
|
||||
'test/interpreter_functional/plugins/*/package.json',
|
||||
],
|
||||
|
|
|
@ -24,4 +24,5 @@ require('@kbn/test').runTestsCli([
|
|||
require.resolve('../test/plugin_functional/config.js'),
|
||||
require.resolve('../test/interpreter_functional/config.ts'),
|
||||
require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'),
|
||||
require.resolve('../test/examples/config.js')
|
||||
]);
|
||||
|
|
|
@ -144,6 +144,11 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
|
|||
set('plugins.paths', _.compact([].concat(
|
||||
get('plugins.paths'),
|
||||
opts.pluginPath,
|
||||
opts.runExamples ? [
|
||||
// Ideally this would automatically include all plugins in the examples dir
|
||||
fromRoot('examples/demo_search'),
|
||||
fromRoot('examples/search_explorer'),
|
||||
] : [],
|
||||
|
||||
XPACK_INSTALLED && !opts.oss
|
||||
? [XPACK_DIR]
|
||||
|
@ -201,7 +206,8 @@ export default function (program) {
|
|||
|
||||
if (!IS_KIBANA_DISTRIBUTABLE) {
|
||||
command
|
||||
.option('--oss', 'Start Kibana without X-Pack');
|
||||
.option('--oss', 'Start Kibana without X-Pack')
|
||||
.option('--run-examples', 'Adds plugin paths for all the Kibana example plugins and runs with no base path');
|
||||
}
|
||||
|
||||
if (CAN_CLUSTER) {
|
||||
|
@ -238,7 +244,12 @@ export default function (program) {
|
|||
silent: !!opts.silent,
|
||||
watch: !!opts.watch,
|
||||
repl: !!opts.repl,
|
||||
basePath: !!opts.basePath,
|
||||
// We want to run without base path when the `--run-examples` flag is given so that we can use local
|
||||
// links in other documentation sources, like "View this tutorial [here](http://localhost:5601/app/tutorial/xyz)".
|
||||
// We can tell users they only have to run with `yarn start --run-examples` to get those
|
||||
// local links to work. Similar to what we do for "View in Console" links in our
|
||||
// elastic.co links.
|
||||
basePath: opts.runExamples ? false : !!opts.basePath,
|
||||
optimize: !!opts.optimize,
|
||||
oss: !!opts.oss
|
||||
},
|
||||
|
|
|
@ -58,6 +58,15 @@ function getKbnPrecommitGitHookScript(rootPath, nodeHome, platform) {
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
# Make it possible to terminate pre commit hook
|
||||
# using ctrl-c so nothing else would happen or be
|
||||
# sent to the output.
|
||||
#
|
||||
# The correct exit code on that situation
|
||||
# according the linux documentation project is 130
|
||||
# https://www.tldp.org/LDP/abs/html/exitcodes.html
|
||||
trap "exit 130" SIGINT
|
||||
|
||||
has_node() {
|
||||
command -v node >/dev/null 2>&1
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ export const PACKAGE_GLOBS = [
|
|||
'x-pack/package.json',
|
||||
'x-pack/legacy/plugins/*/package.json',
|
||||
'packages/*/package.json',
|
||||
'examples/*/package.json',
|
||||
'test/plugin_functional/plugins/*/package.json',
|
||||
'test/interpreter_functional/plugins/*/package.json',
|
||||
];
|
||||
|
|
|
@ -40,6 +40,9 @@ export const PROJECTS = [
|
|||
...glob
|
||||
.sync('packages/*/tsconfig.json', { cwd: REPO_ROOT })
|
||||
.map(path => new Project(resolve(REPO_ROOT, path))),
|
||||
...glob
|
||||
.sync('examples/*/tsconfig.json', { cwd: REPO_ROOT })
|
||||
.map(path => new Project(resolve(REPO_ROOT, path))),
|
||||
...glob
|
||||
.sync('test/plugin_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT })
|
||||
.map(path => new Project(resolve(REPO_ROOT, path))),
|
||||
|
|
|
@ -193,78 +193,160 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `
|
|||
textComponent={Symbol(react.fragment)}
|
||||
>
|
||||
<PseudoLocaleWrapper>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="xxl"
|
||||
type="dashboardApp"
|
||||
<EuiPage
|
||||
className="dshStartScreen"
|
||||
restrictWidth="36em"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
style={null}
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</EuiIconEmpty>
|
||||
</EuiIcon>
|
||||
<h2>
|
||||
This dashboard is empty. Let’s fill it up!
|
||||
</h2>
|
||||
<p>
|
||||
<span>
|
||||
Click the
|
||||
<EuiLink
|
||||
aria-label="Add visualization"
|
||||
data-test-subj="emptyDashboardAddPanelButton"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<button
|
||||
aria-label="Add visualization"
|
||||
className="euiLink euiLink--primary"
|
||||
data-test-subj="emptyDashboardAddPanelButton"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</EuiLink>
|
||||
button in the menu bar above to add a visualization to the dashboard.
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
className="linkToVisualizeParagraph"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you haven't set up any visualizations yet, {visualizeAppLink} to create your first visualization"
|
||||
id="kbn.dashboard.addVisualizationDescription3"
|
||||
values={
|
||||
<div
|
||||
className="euiPage euiPage--restrictWidth-custom dshStartScreen"
|
||||
style={
|
||||
Object {
|
||||
"visualizeAppLink": <a
|
||||
className="euiLink"
|
||||
href="#/visualize"
|
||||
>
|
||||
visit the Visualize app
|
||||
</a>,
|
||||
"maxWidth": "36em",
|
||||
}
|
||||
}
|
||||
>
|
||||
If you haven't set up any visualizations yet,
|
||||
<a
|
||||
className="euiLink"
|
||||
href="#/visualize"
|
||||
<EuiPageBody
|
||||
component="main"
|
||||
restrictWidth={false}
|
||||
>
|
||||
visit the Visualize app
|
||||
</a>
|
||||
to create your first visualization
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
<main
|
||||
className="euiPageBody"
|
||||
>
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
panelPaddingSize="l"
|
||||
verticalPosition="center"
|
||||
>
|
||||
<EuiPanel
|
||||
className="euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter"
|
||||
paddingSize="l"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter"
|
||||
>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="xxl"
|
||||
type="dashboardApp"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
style={null}
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</EuiIconEmpty>
|
||||
</EuiIcon>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText
|
||||
grow={true}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<h2
|
||||
key="0.5"
|
||||
>
|
||||
This dashboard is empty. Let’s fill it up!
|
||||
</h2>
|
||||
</div>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p>
|
||||
Click the
|
||||
<EuiLink
|
||||
aria-label="Add visualization"
|
||||
data-test-subj="emptyDashboardAddPanelButton"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<button
|
||||
aria-label="Add visualization"
|
||||
className="euiLink euiLink--primary"
|
||||
data-test-subj="emptyDashboardAddPanelButton"
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</EuiLink>
|
||||
button in the menu bar above to add a visualization to the dashboard.
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText
|
||||
data-test-subj="linkToVisualizeParagraph"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
data-test-subj="linkToVisualizeParagraph"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you haven't set up any visualizations yet, {visualizeAppLink} to create your first visualization"
|
||||
id="kbn.dashboard.addVisualizationDescription3"
|
||||
values={
|
||||
Object {
|
||||
"visualizeAppLink": <a
|
||||
className="euiLink"
|
||||
href="#/visualize"
|
||||
>
|
||||
visit the Visualize app
|
||||
</a>,
|
||||
}
|
||||
}
|
||||
>
|
||||
If you haven't set up any visualizations yet,
|
||||
<a
|
||||
className="euiLink"
|
||||
href="#/visualize"
|
||||
>
|
||||
visit the Visualize app
|
||||
</a>
|
||||
to create your first visualization
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</EuiPageContent>
|
||||
</main>
|
||||
</EuiPageBody>
|
||||
</div>
|
||||
</EuiPage>
|
||||
</PseudoLocaleWrapper>
|
||||
</IntlProvider>
|
||||
</I18nProvider>
|
||||
|
@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`]
|
|||
textComponent={Symbol(react.fragment)}
|
||||
>
|
||||
<PseudoLocaleWrapper>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="xxl"
|
||||
type="dashboardApp"
|
||||
<EuiPage
|
||||
className="dshStartScreen"
|
||||
restrictWidth="36em"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
style={null}
|
||||
<div
|
||||
className="euiPage euiPage--restrictWidth-custom dshStartScreen"
|
||||
style={
|
||||
Object {
|
||||
"maxWidth": "36em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</EuiIconEmpty>
|
||||
</EuiIcon>
|
||||
<h2>
|
||||
This dashboard is empty. Let’s fill it up!
|
||||
</h2>
|
||||
<p>
|
||||
<span>
|
||||
Click the
|
||||
<EuiLink
|
||||
aria-label="Edit dashboard"
|
||||
data-test-subj=""
|
||||
onClick={[MockFunction]}
|
||||
<EuiPageBody
|
||||
component="main"
|
||||
restrictWidth={false}
|
||||
>
|
||||
<button
|
||||
aria-label="Edit dashboard"
|
||||
className="euiLink euiLink--primary"
|
||||
data-test-subj=""
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
<main
|
||||
className="euiPageBody"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</EuiLink>
|
||||
button in the menu bar above to start working on your new dashboard.
|
||||
</span>
|
||||
</p>
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
panelPaddingSize="l"
|
||||
verticalPosition="center"
|
||||
>
|
||||
<EuiPanel
|
||||
className="euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter"
|
||||
paddingSize="l"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter"
|
||||
>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="xxl"
|
||||
type="dashboardApp"
|
||||
>
|
||||
<EuiIconEmpty
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
style={null}
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</EuiIconEmpty>
|
||||
</EuiIcon>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText
|
||||
grow={true}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<h2
|
||||
key="0.5"
|
||||
>
|
||||
This dashboard is empty. Let’s fill it up!
|
||||
</h2>
|
||||
</div>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p>
|
||||
Click the
|
||||
<EuiLink
|
||||
aria-label="Edit dashboard"
|
||||
data-test-subj=""
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<button
|
||||
aria-label="Edit dashboard"
|
||||
className="euiLink euiLink--primary"
|
||||
data-test-subj=""
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</EuiLink>
|
||||
button in the menu bar above to start working on your new dashboard.
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</EuiPageContent>
|
||||
</main>
|
||||
</EuiPageBody>
|
||||
</div>
|
||||
</EuiPage>
|
||||
</PseudoLocaleWrapper>
|
||||
</IntlProvider>
|
||||
</I18nProvider>
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen';
|
||||
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen';
|
||||
// @ts-ignore
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
describe('DashboardEmptyScreen', () => {
|
||||
const defaultProps = {
|
||||
|
@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => {
|
|||
onLinkClick: jest.fn(),
|
||||
};
|
||||
|
||||
function mountComponent(props?: Props) {
|
||||
function mountComponent(props?: DashboardEmptyScreenProps) {
|
||||
const compProps = props || defaultProps;
|
||||
const comp = mountWithIntl(<DashboardEmptyScreen {...compProps} />);
|
||||
return comp;
|
||||
|
@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => {
|
|||
test('renders correctly with visualize paragraph', () => {
|
||||
const component = mountComponent();
|
||||
expect(component).toMatchSnapshot();
|
||||
const paragraph = component.find('.linkToVisualizeParagraph');
|
||||
const paragraph = findTestSubject(component, 'linkToVisualizeParagraph');
|
||||
expect(paragraph.length).toBe(1);
|
||||
});
|
||||
|
||||
test('renders correctly without visualize paragraph', () => {
|
||||
const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } });
|
||||
expect(component).toMatchSnapshot();
|
||||
const paragraph = component.find('.linkToVisualizeParagraph');
|
||||
const paragraph = findTestSubject(component, 'linkToVisualizeParagraph');
|
||||
expect(paragraph.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,9 +6,5 @@
|
|||
|
||||
.dshStartScreen {
|
||||
text-align: center;
|
||||
padding: $euiSize;
|
||||
|
||||
> * {
|
||||
max-width: 36em !important;
|
||||
}
|
||||
padding: $euiSizeS;
|
||||
}
|
||||
|
|
|
@ -131,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav
|
|||
'app/dashboard/State',
|
||||
'app/dashboard/ConfirmModal',
|
||||
'app/dashboard/icon',
|
||||
'app/dashboard/emptyScreen',
|
||||
]);
|
||||
return dashboardAngularModule;
|
||||
}
|
||||
|
|
|
@ -48,24 +48,6 @@
|
|||
>
|
||||
</kbn-top-nav>
|
||||
|
||||
<div ng-show="getShouldShowEditHelp() || getShouldShowViewHelp()" class="dshStartScreen">
|
||||
<div class="euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--horizontalCenter">
|
||||
<br><br>
|
||||
<div ng-show="getShouldShowEditHelp()" class="euiText">
|
||||
<dashboard-empty-screen on-link-click="showAddPanel"
|
||||
show-link-to-visualize="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div ng-show="getShouldShowViewHelp()" class="euiText">
|
||||
<dashboard-empty-screen show-link-to-visualize="false"
|
||||
on-link-click="enterEditMode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="euiScreenReaderOnly">{{screenTitle}}</h1>
|
||||
<div id="dashboardViewport"></div>
|
||||
|
||||
|
|
|
@ -21,9 +21,10 @@ import _ from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import angular from 'angular';
|
||||
import { uniq } from 'lodash';
|
||||
import { uniq, noop } from 'lodash';
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
|
||||
|
||||
import {
|
||||
subscribeWithScope,
|
||||
|
@ -35,14 +36,11 @@ import {
|
|||
AppStateClass as TAppStateClass,
|
||||
KbnUrl,
|
||||
SaveOptions,
|
||||
SavedObjectFinder,
|
||||
unhashUrl,
|
||||
} from './legacy_imports';
|
||||
import { FilterStateManager, IndexPattern } from '../../../data/public';
|
||||
import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public';
|
||||
|
||||
import './dashboard_empty_screen_directive';
|
||||
|
||||
import {
|
||||
DashboardContainer,
|
||||
DASHBOARD_CONTAINER_TYPE,
|
||||
|
@ -71,6 +69,10 @@ import { DashboardAppScope } from './dashboard_app';
|
|||
import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable';
|
||||
import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
|
||||
import { RenderDeps } from './application';
|
||||
import {
|
||||
SavedObjectFinderProps,
|
||||
SavedObjectFinderUi,
|
||||
} from '../../../../../plugins/kibana_react/public';
|
||||
|
||||
export interface DashboardAppControllerDependencies extends RenderDeps {
|
||||
$scope: DashboardAppScope;
|
||||
|
@ -115,7 +117,7 @@ export class DashboardAppController {
|
|||
timefilter: { timefilter },
|
||||
},
|
||||
},
|
||||
core: { notifications, overlays, chrome, injectedMetadata },
|
||||
core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects },
|
||||
}: DashboardAppControllerDependencies) {
|
||||
new FilterStateManager(globalState, getAppState, filterManager);
|
||||
const queryFilter = filterManager;
|
||||
|
@ -143,6 +145,16 @@ export class DashboardAppController {
|
|||
}
|
||||
$scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean;
|
||||
|
||||
$scope.getShouldShowEditHelp = () =>
|
||||
!dashboardStateManager.getPanels().length &&
|
||||
dashboardStateManager.getIsEditMode() &&
|
||||
!dashboardConfig.getHideWriteControls();
|
||||
|
||||
$scope.getShouldShowViewHelp = () =>
|
||||
!dashboardStateManager.getPanels().length &&
|
||||
dashboardStateManager.getIsViewMode() &&
|
||||
!dashboardConfig.getHideWriteControls();
|
||||
|
||||
const updateIndexPatterns = (container?: DashboardContainer) => {
|
||||
if (!container || isErrorEmbeddable(container)) {
|
||||
return;
|
||||
|
@ -171,6 +183,17 @@ export class DashboardAppController {
|
|||
}
|
||||
};
|
||||
|
||||
const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => {
|
||||
const emptyScreenProps: DashboardEmptyScreenProps = {
|
||||
onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode,
|
||||
showLinkToVisualize: shouldShowEditHelp,
|
||||
};
|
||||
if (shouldShowEditHelp) {
|
||||
emptyScreenProps.onVisualizeClick = noop;
|
||||
}
|
||||
return emptyScreenProps;
|
||||
};
|
||||
|
||||
const getDashboardInput = (): DashboardContainerInput => {
|
||||
const embeddablesMap: {
|
||||
[key: string]: DashboardPanelState;
|
||||
|
@ -182,6 +205,8 @@ export class DashboardAppController {
|
|||
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
|
||||
expandedPanelId = dashboardContainer.getInput().expandedPanelId;
|
||||
}
|
||||
const shouldShowEditHelp = $scope.getShouldShowEditHelp();
|
||||
const shouldShowViewHelp = $scope.getShouldShowViewHelp();
|
||||
return {
|
||||
id: dashboardStateManager.savedDashboard.id || '',
|
||||
filters: queryFilter.getFilters(),
|
||||
|
@ -194,6 +219,7 @@ export class DashboardAppController {
|
|||
viewMode: dashboardStateManager.getViewMode(),
|
||||
panels: embeddablesMap,
|
||||
isFullScreenMode: dashboardStateManager.getFullScreenMode(),
|
||||
isEmptyState: shouldShowEditHelp || shouldShowViewHelp,
|
||||
useMargins: dashboardStateManager.getUseMargins(),
|
||||
lastReloadRequestTime,
|
||||
title: dashboardStateManager.getTitle(),
|
||||
|
@ -234,6 +260,15 @@ export class DashboardAppController {
|
|||
if (!isErrorEmbeddable(container)) {
|
||||
dashboardContainer = container;
|
||||
|
||||
dashboardContainer.renderEmpty = () => {
|
||||
const shouldShowEditHelp = $scope.getShouldShowEditHelp();
|
||||
const shouldShowViewHelp = $scope.getShouldShowViewHelp();
|
||||
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp;
|
||||
return isEmptyState ? (
|
||||
<DashboardEmptyScreen {...getEmptyScreenProps(shouldShowEditHelp)} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
updateIndexPatterns(dashboardContainer);
|
||||
|
||||
outputSubscription = dashboardContainer.getOutput$().subscribe(() => {
|
||||
|
@ -334,15 +369,6 @@ export class DashboardAppController {
|
|||
updateBreadcrumbs();
|
||||
dashboardStateManager.registerChangeListener(updateBreadcrumbs);
|
||||
|
||||
$scope.getShouldShowEditHelp = () =>
|
||||
!dashboardStateManager.getPanels().length &&
|
||||
dashboardStateManager.getIsEditMode() &&
|
||||
!dashboardConfig.getHideWriteControls();
|
||||
$scope.getShouldShowViewHelp = () =>
|
||||
!dashboardStateManager.getPanels().length &&
|
||||
dashboardStateManager.getIsViewMode() &&
|
||||
!dashboardConfig.getHideWriteControls();
|
||||
|
||||
const getChangesFromAppStateForContainerState = () => {
|
||||
const appStateDashboardInput = getDashboardInput();
|
||||
if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) {
|
||||
|
@ -718,6 +744,10 @@ export class DashboardAppController {
|
|||
};
|
||||
navActions[TopNavIds.ADD] = () => {
|
||||
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
|
||||
const SavedObjectFinder = (props: SavedObjectFinderProps) => (
|
||||
<SavedObjectFinderUi {...props} savedObjects={savedObjects} uiSettings={uiSettings} />
|
||||
);
|
||||
|
||||
openAddPanelFlyout({
|
||||
embeddable: dashboardContainer,
|
||||
getAllFactories: embeddables.getEmbeddableFactories,
|
||||
|
@ -729,6 +759,8 @@ export class DashboardAppController {
|
|||
}
|
||||
};
|
||||
|
||||
navActions[TopNavIds.VISUALIZE] = async () => {};
|
||||
|
||||
navActions[TopNavIds.OPTIONS] = anchorElement => {
|
||||
showOptionsPopover({
|
||||
anchorElement,
|
||||
|
|
|
@ -18,29 +18,43 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiPageContent,
|
||||
EuiPageBody,
|
||||
EuiPage,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import * as constants from './dashboard_empty_screen_constants';
|
||||
|
||||
export interface Props {
|
||||
export interface DashboardEmptyScreenProps {
|
||||
showLinkToVisualize: boolean;
|
||||
onLinkClick: () => void;
|
||||
onVisualizeClick?: () => void;
|
||||
}
|
||||
|
||||
export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) {
|
||||
export function DashboardEmptyScreen({
|
||||
showLinkToVisualize,
|
||||
onLinkClick,
|
||||
}: DashboardEmptyScreenProps) {
|
||||
const linkToVisualizeParagraph = (
|
||||
<p className="linkToVisualizeParagraph">
|
||||
<FormattedMessage
|
||||
id="kbn.dashboard.addVisualizationDescription3"
|
||||
defaultMessage="If you haven't set up any visualizations yet, {visualizeAppLink} to create your first visualization"
|
||||
values={{
|
||||
visualizeAppLink: (
|
||||
<a className="euiLink" href="#/visualize">
|
||||
{constants.visualizeAppLinkTest}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiText data-test-subj="linkToVisualizeParagraph">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.dashboard.addVisualizationDescription3"
|
||||
defaultMessage="If you haven't set up any visualizations yet, {visualizeAppLink} to create your first visualization"
|
||||
values={{
|
||||
visualizeAppLink: (
|
||||
<a className="euiLink" href="#/visualize">
|
||||
{constants.visualizeAppLinkTest}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
const paragraph = (
|
||||
description1: string,
|
||||
|
@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props
|
|||
dataTestSubj?: string
|
||||
) => {
|
||||
return (
|
||||
<p>
|
||||
<span>
|
||||
<EuiText size="m">
|
||||
<p>
|
||||
{description1}
|
||||
<EuiLink onClick={onLinkClick} aria-label={ariaLabel} data-test-subj={dataTestSubj || ''}>
|
||||
{linkText}
|
||||
</EuiLink>
|
||||
{description2}
|
||||
</span>
|
||||
</p>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
const addVisualizationParagraph = (
|
||||
|
@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props
|
|||
constants.addVisualizationLinkAriaLabel,
|
||||
'emptyDashboardAddPanelButton'
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{linkToVisualizeParagraph}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props
|
|||
);
|
||||
return (
|
||||
<I18nProvider>
|
||||
<React.Fragment>
|
||||
<EuiIcon type="dashboardApp" size="xxl" color="subdued" />
|
||||
<h2>{constants.fillDashboardTitle}</h2>
|
||||
{showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph}
|
||||
</React.Fragment>
|
||||
<EuiPage className="dshStartScreen" restrictWidth={'36em'}>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
||||
<EuiIcon type="dashboardApp" size="xxl" color="subdued" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText grow={true}>
|
||||
<h2 key={0.5}>{constants.fillDashboardTitle}</h2>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
{showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph}
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +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.
|
||||
*/
|
||||
// @ts-ignore
|
||||
import angular from 'angular';
|
||||
import { DashboardEmptyScreen } from './dashboard_empty_screen';
|
||||
|
||||
angular
|
||||
.module('app/dashboard/emptyScreen', ['react'])
|
||||
.directive('dashboardEmptyScreen', function(reactDirective: any) {
|
||||
return reactDirective(DashboardEmptyScreen, [
|
||||
['showLinkToVisualize', { watchDepth: 'value' }],
|
||||
['onLinkClick', { watchDepth: 'reference' }],
|
||||
]);
|
||||
});
|
|
@ -65,4 +65,3 @@ export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_mon
|
|||
export { ensureDefaultIndexPattern } from 'ui/legacy_compat';
|
||||
export { unhashUrl } from '../../../../../plugins/kibana_utils/public';
|
||||
export { IInjector } from 'ui/chrome';
|
||||
export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
|
|
|
@ -26,4 +26,5 @@ export const TopNavIds = {
|
|||
ENTER_EDIT_MODE: 'enterEditMode',
|
||||
CLONE: 'clone',
|
||||
FULL_SCREEN: 'fullScreenMode',
|
||||
VISUALIZE: 'visualize',
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ exports[`render 1`] = `
|
|||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<SavedObjectFinder
|
||||
<SavedObjectFinderUi
|
||||
noItemsMessage={
|
||||
<FormattedMessage
|
||||
defaultMessage="No matching searches found."
|
||||
|
@ -44,6 +44,8 @@ exports[`render 1`] = `
|
|||
},
|
||||
]
|
||||
}
|
||||
savedObjects={Object {}}
|
||||
uiSettings={Object {}}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -32,11 +32,16 @@ import {
|
|||
EuiFlyoutBody,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public';
|
||||
import { getServices } from '../../kibana_services';
|
||||
|
||||
const SEARCH_OBJECT_TYPE = 'search';
|
||||
|
||||
export function OpenSearchPanel(props) {
|
||||
const {
|
||||
core: { uiSettings, savedObjects },
|
||||
} = getServices();
|
||||
|
||||
return (
|
||||
<EuiFlyout ownFocus onClose={props.onClose} data-test-subj="loadSearchForm">
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
|
@ -50,7 +55,7 @@ export function OpenSearchPanel(props) {
|
|||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<SavedObjectFinder
|
||||
<SavedObjectFinderUi
|
||||
noItemsMessage={
|
||||
<FormattedMessage
|
||||
id="kbn.discover.topNav.openSearchPanel.noSearchesFoundDescription"
|
||||
|
@ -70,6 +75,8 @@ export function OpenSearchPanel(props) {
|
|||
window.location.assign(props.makeUrl(id));
|
||||
props.onClose();
|
||||
}}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={savedObjects}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -23,7 +23,7 @@ import { shallow } from 'enzyme';
|
|||
jest.mock('../../kibana_services', () => {
|
||||
return {
|
||||
getServices: () => ({
|
||||
SavedObjectFinder: jest.fn()
|
||||
core: { uiSettings: {}, savedObjects: {} },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -199,7 +199,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
|
|||
editorParams: ['addToDashboard'],
|
||||
},
|
||||
npStart.core.http.basePath.prepend,
|
||||
npStart.core.uiSettings
|
||||
npStart.core.uiSettings,
|
||||
npStart.core.savedObjects
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
|
|
|
@ -45,7 +45,6 @@ export { PrivateProvider } from 'ui/private/private';
|
|||
|
||||
export { SavedObjectRegistryProvider } from 'ui/saved_objects';
|
||||
export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
|
||||
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
vis-types-registry="listingController.visTypeRegistry"
|
||||
add-base-path="listingController.addBasePath"
|
||||
ui-settings="listingController.uiSettings"
|
||||
saved-objects="listingController.savedObjects"
|
||||
></new-vis-modal>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -34,6 +34,7 @@ export function initListingDirective(app) {
|
|||
['onClose', { watchDepth: 'reference' }],
|
||||
['addBasePath', { watchDepth: 'reference' }],
|
||||
['uiSettings', { watchDepth: 'reference' }],
|
||||
['savedObjects', { watchDepth: 'reference' }],
|
||||
'isOpen',
|
||||
])
|
||||
);
|
||||
|
@ -54,7 +55,7 @@ export function VisualizeListingController($injector, createNewVis) {
|
|||
toastNotifications,
|
||||
uiSettings,
|
||||
visualizations,
|
||||
core: { docLinks },
|
||||
core: { docLinks, savedObjects },
|
||||
} = getServices();
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
|
||||
|
@ -64,6 +65,7 @@ export function VisualizeListingController($injector, createNewVis) {
|
|||
this.showNewVisModal = false;
|
||||
this.addBasePath = addBasePath;
|
||||
this.uiSettings = uiSettings;
|
||||
this.savedObjects = savedObjects;
|
||||
|
||||
this.createNewVis = () => {
|
||||
this.showNewVisModal = true;
|
||||
|
|
|
@ -108,6 +108,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
}
|
||||
isOpen={true}
|
||||
onClose={[Function]}
|
||||
savedObjects={Object {}}
|
||||
uiSettings={
|
||||
Object {
|
||||
"get": [MockFunction] {
|
||||
|
@ -1413,6 +1414,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
}
|
||||
isOpen={true}
|
||||
onClose={[Function]}
|
||||
savedObjects={Object {}}
|
||||
uiSettings={
|
||||
Object {
|
||||
"get": [MockFunction] {
|
||||
|
|
|
@ -29,6 +29,7 @@ jest.mock('../legacy_imports', () => ({
|
|||
}));
|
||||
|
||||
import { NewVisModal } from './new_vis_modal';
|
||||
import { SavedObjectsStart } from 'kibana/public';
|
||||
|
||||
describe('NewVisModal', () => {
|
||||
const defaultVisTypeParams = {
|
||||
|
@ -76,6 +77,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -89,6 +91,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true);
|
||||
|
@ -104,6 +107,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
const visButton = wrapper.find('button[data-test-subj="visType-vis"]');
|
||||
|
@ -121,6 +125,7 @@ describe('NewVisModal', () => {
|
|||
editorParams={['foo=true', 'bar=42']}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
const visButton = wrapper.find('button[data-test-subj="visType-vis"]');
|
||||
|
@ -138,6 +143,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
const searchBox = wrapper.find('input[data-test-subj="filterVisType"]');
|
||||
|
@ -156,6 +162,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false);
|
||||
|
@ -170,6 +177,7 @@ describe('NewVisModal', () => {
|
|||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={{} as SavedObjectsStart}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true);
|
||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
|||
import { EuiModal, EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public';
|
||||
import { VisType } from '../legacy_imports';
|
||||
import { VisualizeConstants } from '../visualize_constants';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public';
|
||||
|
@ -37,6 +37,7 @@ interface TypeSelectionProps {
|
|||
editorParams?: string[];
|
||||
addBasePath: (path: string) => string;
|
||||
uiSettings: IUiSettingsClient;
|
||||
savedObjects: SavedObjectsStart;
|
||||
}
|
||||
|
||||
interface TypeSelectionState {
|
||||
|
@ -81,7 +82,12 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
const selectionModal =
|
||||
this.state.showSearchVisModal && this.state.visType ? (
|
||||
<EuiModal onClose={this.onCloseModal} className="visNewVisSearchDialog">
|
||||
<SearchSelection onSearchSelected={this.onSearchSelected} visType={this.state.visType} />
|
||||
<SearchSelection
|
||||
onSearchSelected={this.onSearchSelected}
|
||||
visType={this.state.visType}
|
||||
uiSettings={this.props.uiSettings}
|
||||
savedObjects={this.props.savedObjects}
|
||||
/>
|
||||
</EuiModal>
|
||||
) : (
|
||||
<EuiModal
|
||||
|
|
|
@ -21,12 +21,16 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public';
|
||||
|
||||
import { SavedObjectFinder, VisType } from '../../legacy_imports';
|
||||
import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public';
|
||||
import { VisType } from '../../legacy_imports';
|
||||
|
||||
interface SearchSelectionProps {
|
||||
onSearchSelected: (searchId: string, searchType: string) => void;
|
||||
visType: VisType;
|
||||
uiSettings: IUiSettingsClient;
|
||||
savedObjects: SavedObjectsStart;
|
||||
}
|
||||
|
||||
export class SearchSelection extends React.Component<SearchSelectionProps> {
|
||||
|
@ -50,7 +54,7 @@ export class SearchSelection extends React.Component<SearchSelectionProps> {
|
|||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<SavedObjectFinder
|
||||
<SavedObjectFinderUi
|
||||
key="searchSavedObjectFinder"
|
||||
onChoose={this.props.onSearchSelected}
|
||||
showFilter
|
||||
|
@ -83,6 +87,8 @@ export class SearchSelection extends React.Component<SearchSelectionProps> {
|
|||
},
|
||||
]}
|
||||
fixedPageSize={this.fixedPageSize}
|
||||
uiSettings={this.props.uiSettings}
|
||||
savedObjects={this.props.savedObjects}
|
||||
/>
|
||||
</EuiModalBody>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -21,7 +21,7 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public';
|
||||
import { NewVisModal } from './new_vis_modal';
|
||||
import { TypesStart } from '../../../../visualizations/public/np_ready/public/types';
|
||||
|
||||
|
@ -33,7 +33,8 @@ export function showNewVisModal(
|
|||
visTypeRegistry: TypesStart,
|
||||
{ editorParams = [] }: ShowNewVisModalParams = {},
|
||||
addBasePath: (path: string) => string,
|
||||
uiSettings: IUiSettingsClient
|
||||
uiSettings: IUiSettingsClient,
|
||||
savedObjects: SavedObjectsStart
|
||||
) {
|
||||
const container = document.createElement('div');
|
||||
const onClose = () => {
|
||||
|
@ -51,6 +52,7 @@ export function showNewVisModal(
|
|||
editorParams={editorParams}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
savedObjects={savedObjects}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -38,7 +38,24 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1`
|
|||
"defVal": true,
|
||||
"description": <React.Fragment>
|
||||
<p>
|
||||
Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.
|
||||
<FormattedMessage
|
||||
defaultMessage="Enabling data usage collection helps us manage and improve our products and services. See our {privacyStatementLink} for more details."
|
||||
id="telemetry.telemetryConfigAndLinkDescription"
|
||||
values={
|
||||
Object {
|
||||
"privacyStatementLink": <ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Privacy Statement"
|
||||
id="telemetry.readOurUsageDataPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<ForwardRef
|
||||
|
@ -51,18 +68,6 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1`
|
|||
/>
|
||||
</ForwardRef>
|
||||
</p>
|
||||
<p>
|
||||
<ForwardRef
|
||||
href="https://www.elastic.co/legal/privacy-statement"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Read our usage data privacy statement"
|
||||
id="telemetry.readOurUsageDataPrivacyStatementLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>
|
||||
</p>
|
||||
</React.Fragment>,
|
||||
"type": "boolean",
|
||||
"value": false,
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
import { PRIVACY_STATEMENT_URL } from '../../common/constants';
|
||||
import { OptInExampleFlyout } from './opt_in_details_component';
|
||||
import { Field } from 'ui/management';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -162,7 +162,23 @@ export class TelemetryForm extends Component {
|
|||
|
||||
renderDescription = () => (
|
||||
<Fragment>
|
||||
<p>{getConfigTelemetryDesc()}</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="telemetry.telemetryConfigAndLinkDescription"
|
||||
defaultMessage="Enabling data usage collection helps us manage and improve our products and services.
|
||||
See our {privacyStatementLink} for more details."
|
||||
values={{
|
||||
privacyStatementLink: (
|
||||
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank">
|
||||
<FormattedMessage
|
||||
id="telemetry.readOurUsageDataPrivacyStatementLinkText"
|
||||
defaultMessage="Privacy Statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<EuiLink onClick={this.toggleExample}>
|
||||
<FormattedMessage
|
||||
|
@ -171,14 +187,6 @@ export class TelemetryForm extends Component {
|
|||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
<p>
|
||||
<EuiLink href={PRIVACY_STATEMENT_URL} target="_blank">
|
||||
<FormattedMessage
|
||||
id="telemetry.readOurUsageDataPrivacyStatementLinkText"
|
||||
defaultMessage="Read our usage data privacy statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,108 +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 React from 'react';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { SavedObjectAttributes } from 'src/core/server';
|
||||
import { SimpleSavedObject } from 'src/core/public';
|
||||
import { SavedObjectFinder as SavedObjectFinderNP } from '../../../../../plugins/kibana_react/public';
|
||||
|
||||
/**
|
||||
* DO NOT USE THIS COMPONENT, IT IS DEPRECATED.
|
||||
* Use the one in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
|
||||
export interface SavedObjectMetaData<T extends SavedObjectAttributes> {
|
||||
type: string;
|
||||
name: string;
|
||||
getIconForSavedObject(savedObject: SimpleSavedObject<T>): IconType;
|
||||
getTooltipForSavedObject?(savedObject: SimpleSavedObject<T>): string;
|
||||
showSavedObject?(savedObject: SimpleSavedObject<T>): boolean;
|
||||
}
|
||||
|
||||
interface BaseSavedObjectFinder {
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
onChoose?: (
|
||||
id: SimpleSavedObject<SavedObjectAttributes>['id'],
|
||||
type: SimpleSavedObject<SavedObjectAttributes>['type'],
|
||||
name: string
|
||||
) => void;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
noItemsMessage?: React.ReactNode;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
savedObjectMetaData: Array<SavedObjectMetaData<SavedObjectAttributes>>;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
showFilter?: boolean;
|
||||
}
|
||||
|
||||
interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder {
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
initialPageSize?: undefined;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
fixedPageSize: number;
|
||||
}
|
||||
|
||||
interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder {
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
initialPageSize?: 5 | 10 | 15 | 25;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use component in `src/plugins/kibana_react` instead.
|
||||
*/
|
||||
fixedPageSize?: undefined;
|
||||
}
|
||||
type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize;
|
||||
|
||||
export const SavedObjectFinder: React.FC<SavedObjectFinderProps> = props => (
|
||||
<SavedObjectFinderNP
|
||||
savedObjects={npStart.core.savedObjects}
|
||||
uiSettings={npStart.core.uiSettings}
|
||||
{...props}
|
||||
/>
|
||||
);
|
|
@ -90,6 +90,8 @@ export type DashboardReactContext = KibanaReactContext<DashboardContainerOptions
|
|||
export class DashboardContainer extends Container<InheritedChildInput, DashboardContainerInput> {
|
||||
public readonly type = DASHBOARD_CONTAINER_TYPE;
|
||||
|
||||
public renderEmpty?: undefined | (() => React.ReactNode);
|
||||
|
||||
constructor(
|
||||
initialInput: DashboardContainerInput,
|
||||
private readonly options: DashboardContainerOptions,
|
||||
|
@ -124,7 +126,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={this.options}>
|
||||
<DashboardViewport container={this} />
|
||||
<DashboardViewport renderEmpty={this.renderEmpty} container={this} />
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>,
|
||||
dom
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
.dshLayout-isMaximizedPanel {
|
||||
height: 100% !important; /* 1. */
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -121,6 +121,24 @@ test('renders DashboardViewport with no visualizations', () => {
|
|||
component.unmount();
|
||||
});
|
||||
|
||||
test('renders DashboardEmptyScreen', () => {
|
||||
const renderEmptyScreen = jest.fn();
|
||||
const { props, options } = getProps({ renderEmpty: renderEmptyScreen });
|
||||
props.container.updateInput({ isEmptyState: true });
|
||||
const component = mount(
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={options}>
|
||||
<DashboardViewport {...props} />
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen');
|
||||
expect(dashboardEmptyScreenDiv.length).toBe(1);
|
||||
expect(renderEmptyScreen).toHaveBeenCalled();
|
||||
|
||||
component.unmount();
|
||||
});
|
||||
|
||||
test('renders exit full screen button when in full screen mode', async () => {
|
||||
const { props, options } = getProps();
|
||||
props.container.updateInput({ isFullScreenMode: true });
|
||||
|
@ -153,6 +171,39 @@ test('renders exit full screen button when in full screen mode', async () => {
|
|||
component.unmount();
|
||||
});
|
||||
|
||||
test('renders exit full screen button when in full screen mode and empty screen', async () => {
|
||||
const renderEmptyScreen = jest.fn();
|
||||
renderEmptyScreen.mockReturnValue(React.createElement('div'));
|
||||
const { props, options } = getProps({ renderEmpty: renderEmptyScreen });
|
||||
props.container.updateInput({ isEmptyState: true, isFullScreenMode: true });
|
||||
const component = mount(
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={options}>
|
||||
<DashboardViewport {...props} />
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
expect(
|
||||
(component
|
||||
.find('.dshDashboardEmptyScreen')
|
||||
.childAt(0)
|
||||
.type() as any).name
|
||||
).toBe('ExitFullScreenButton');
|
||||
|
||||
props.container.updateInput({ isFullScreenMode: false });
|
||||
component.update();
|
||||
await nextTick();
|
||||
|
||||
expect(
|
||||
(component
|
||||
.find('.dshDashboardEmptyScreen')
|
||||
.childAt(0)
|
||||
.type() as any).name
|
||||
).not.toBe('ExitFullScreenButton');
|
||||
|
||||
component.unmount();
|
||||
});
|
||||
|
||||
test('DashboardViewport unmount unsubscribes', async done => {
|
||||
const { props, options } = getProps();
|
||||
const component = mount(
|
||||
|
|
|
@ -26,6 +26,7 @@ import { context } from '../../../../kibana_react/public';
|
|||
|
||||
export interface DashboardViewportProps {
|
||||
container: DashboardContainer;
|
||||
renderEmpty?: () => React.ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -34,6 +35,7 @@ interface State {
|
|||
title: string;
|
||||
description?: string;
|
||||
panels: { [key: string]: PanelState };
|
||||
isEmptyState?: boolean;
|
||||
}
|
||||
|
||||
export class DashboardViewport extends React.Component<DashboardViewportProps, State> {
|
||||
|
@ -44,26 +46,40 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
|
|||
private mounted: boolean = false;
|
||||
constructor(props: DashboardViewportProps) {
|
||||
super(props);
|
||||
const { isFullScreenMode, panels, useMargins, title } = this.props.container.getInput();
|
||||
const {
|
||||
isFullScreenMode,
|
||||
panels,
|
||||
useMargins,
|
||||
title,
|
||||
isEmptyState,
|
||||
} = this.props.container.getInput();
|
||||
|
||||
this.state = {
|
||||
isFullScreenMode,
|
||||
panels,
|
||||
useMargins,
|
||||
title,
|
||||
isEmptyState,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.subscription = this.props.container.getInput$().subscribe(() => {
|
||||
const { isFullScreenMode, useMargins, title, description } = this.props.container.getInput();
|
||||
const {
|
||||
isFullScreenMode,
|
||||
useMargins,
|
||||
title,
|
||||
description,
|
||||
isEmptyState,
|
||||
} = this.props.container.getInput();
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
isFullScreenMode,
|
||||
description,
|
||||
useMargins,
|
||||
title,
|
||||
isEmptyState,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -82,19 +98,33 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
|
|||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
private renderEmptyScreen() {
|
||||
const { renderEmpty } = this.props;
|
||||
const { isFullScreenMode } = this.state;
|
||||
return (
|
||||
<div className="dshDashboardEmptyScreen">
|
||||
{isFullScreenMode && (
|
||||
<this.context.services.ExitFullScreenButton
|
||||
onExitFullScreenMode={this.onExitFullScreenMode}
|
||||
/>
|
||||
)}
|
||||
{renderEmpty && renderEmpty()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContainerScreen() {
|
||||
const { container } = this.props;
|
||||
const { isFullScreenMode, panels, title, description, useMargins } = this.state;
|
||||
return (
|
||||
<div
|
||||
data-shared-items-count={Object.values(this.state.panels).length}
|
||||
data-shared-items-count={Object.values(panels).length}
|
||||
data-shared-items-container
|
||||
data-title={this.state.title}
|
||||
data-description={this.state.description}
|
||||
className={
|
||||
this.state.useMargins ? 'dshDashboardViewport-withMargins' : 'dshDashboardViewport'
|
||||
}
|
||||
data-title={title}
|
||||
data-description={description}
|
||||
className={useMargins ? 'dshDashboardViewport-withMargins' : 'dshDashboardViewport'}
|
||||
>
|
||||
{this.state.isFullScreenMode && (
|
||||
{isFullScreenMode && (
|
||||
<this.context.services.ExitFullScreenButton
|
||||
onExitFullScreenMode={this.onExitFullScreenMode}
|
||||
/>
|
||||
|
@ -103,4 +133,13 @@ export class DashboardViewport extends React.Component<DashboardViewportProps, S
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.state.isEmptyState ? this.renderEmptyScreen() : null}
|
||||
{this.renderContainerScreen()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { ExpandPanelAction, ReplacePanelAction } from '.';
|
|||
import { DashboardContainerFactory } from './embeddable/dashboard_container_factory';
|
||||
import { Start as InspectorStartContract } from '../../../plugins/inspector/public';
|
||||
import {
|
||||
SavedObjectFinder as SavedObjectFinderUi,
|
||||
SavedObjectFinderUi,
|
||||
SavedObjectFinderProps,
|
||||
ExitFullScreenButton as ExitFullScreenButtonUi,
|
||||
ExitFullScreenButtonProps,
|
||||
|
|
|
@ -240,6 +240,7 @@ export abstract class Container<
|
|||
...this.input.panels,
|
||||
[panelState.explicitInput.id]: panelState,
|
||||
},
|
||||
isEmptyState: false,
|
||||
} as Partial<TContainerInput>);
|
||||
|
||||
return await this.untilEmbeddableLoaded<TEmbeddable>(panelState.explicitInput.id);
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface EmbeddableInput {
|
|||
id: string;
|
||||
lastReloadRequestTime?: number;
|
||||
hidePanelTitles?: boolean;
|
||||
|
||||
isEmptyState?: boolean;
|
||||
/**
|
||||
* List of action IDs that this embeddable should not render.
|
||||
*/
|
||||
|
|
|
@ -35,7 +35,7 @@ import { IconType } from '@elastic/eui';
|
|||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import * as sinon from 'sinon';
|
||||
import { SavedObjectFinder } from './saved_object_finder';
|
||||
import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder';
|
||||
// eslint-disable-next-line
|
||||
import { coreMock } from '../../../../core/public/mocks';
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { SavedObjectAttributes } from '../../../../core/server';
|
||||
import { SimpleSavedObject, CoreStart } from '../../../../core/public';
|
||||
import { useKibana } from '../context';
|
||||
|
||||
// TODO the typings for EuiListGroup are incorrect - maxWidth is missing. This can be removed when the types are adjusted
|
||||
const FixedEuiListGroup = (EuiListGroup as any) as React.FunctionComponent<
|
||||
|
@ -104,12 +105,18 @@ interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder {
|
|||
initialPageSize?: 5 | 10 | 15 | 25;
|
||||
fixedPageSize?: undefined;
|
||||
}
|
||||
export type SavedObjectFinderProps = {
|
||||
|
||||
export type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize;
|
||||
|
||||
export type SavedObjectFinderUiProps = {
|
||||
savedObjects: CoreStart['savedObjects'];
|
||||
uiSettings: CoreStart['uiSettings'];
|
||||
} & (SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize);
|
||||
} & SavedObjectFinderProps;
|
||||
|
||||
class SavedObjectFinder extends React.Component<SavedObjectFinderProps, SavedObjectFinderState> {
|
||||
class SavedObjectFinderUi extends React.Component<
|
||||
SavedObjectFinderUiProps,
|
||||
SavedObjectFinderState
|
||||
> {
|
||||
public static propTypes = {
|
||||
onChoose: PropTypes.func,
|
||||
noItemsMessage: PropTypes.node,
|
||||
|
@ -174,7 +181,7 @@ class SavedObjectFinder extends React.Component<SavedObjectFinderProps, SavedObj
|
|||
}
|
||||
}, 300);
|
||||
|
||||
constructor(props: SavedObjectFinderProps) {
|
||||
constructor(props: SavedObjectFinderUiProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -523,4 +530,15 @@ class SavedObjectFinder extends React.Component<SavedObjectFinderProps, SavedObj
|
|||
}
|
||||
}
|
||||
|
||||
export { SavedObjectFinder };
|
||||
const SavedObjectFinder = (props: SavedObjectFinderProps) => {
|
||||
const { services } = useKibana();
|
||||
return (
|
||||
<SavedObjectFinderUi
|
||||
{...props}
|
||||
savedObjects={services.savedObject}
|
||||
uiSettings={services.uiSettings}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { SavedObjectFinder, SavedObjectFinderUi };
|
||||
|
|
23
test/examples/README.md
Normal file
23
test/examples/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Example plugin functional tests
|
||||
|
||||
This folder contains functional tests for the example plugins.
|
||||
|
||||
## Run the test
|
||||
|
||||
To run these tests during development you can use the following commands:
|
||||
|
||||
```
|
||||
# Start the test server (can continue running)
|
||||
node scripts/functional_tests_server.js --config test/examples/config.js
|
||||
# Start a test run
|
||||
node scripts/functional_test_runner.js --config test/examples/config.js
|
||||
```
|
||||
|
||||
## Run Kibana with a test plugin
|
||||
|
||||
In case you want to start Kibana with the example plugins, you can just run:
|
||||
|
||||
```
|
||||
yarn start --run-examples
|
||||
```
|
||||
|
55
test/examples/config.js
Normal file
55
test/examples/config.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { services } from '../plugin_functional/services';
|
||||
|
||||
export default async function ({ readConfigFile }) {
|
||||
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
|
||||
|
||||
return {
|
||||
testFiles: [
|
||||
require.resolve('./search'),
|
||||
],
|
||||
services: {
|
||||
...functionalConfig.get('services'),
|
||||
...services,
|
||||
},
|
||||
pageObjects: functionalConfig.get('pageObjects'),
|
||||
servers: functionalConfig.get('servers'),
|
||||
esTestCluster: functionalConfig.get('esTestCluster'),
|
||||
apps: functionalConfig.get('apps'),
|
||||
esArchiver: {
|
||||
directory: path.resolve(__dirname, '../es_archives')
|
||||
},
|
||||
screenshots: functionalConfig.get('screenshots'),
|
||||
junit: {
|
||||
reportName: 'Example plugin functional tests',
|
||||
},
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('kbnTestServer.serverArgs'),
|
||||
'--run-examples',
|
||||
// Required to run examples
|
||||
'--env.name=development',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
|
@ -78,7 +78,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton();
|
||||
await logoButton.moveMouseTo();
|
||||
await PageObjects.dashboard.clickExitFullScreenTextButton();
|
||||
|
||||
await retry.try(async () => {
|
||||
const isChromeVisible = await PageObjects.common.isChromeVisible();
|
||||
expect(isChromeVisible).to.be(true);
|
||||
|
|
|
@ -33,7 +33,6 @@ export default async function ({ readConfigFile }) {
|
|||
require.resolve('./test_suites/app_plugins'),
|
||||
require.resolve('./test_suites/custom_visualizations'),
|
||||
require.resolve('./test_suites/panel_actions'),
|
||||
require.resolve('./test_suites/search'),
|
||||
|
||||
/**
|
||||
* @todo Work on re-enabling this test suite after this is merged. These tests pass
|
||||
|
|
|
@ -22,7 +22,6 @@ import 'uiExports/embeddableFactories';
|
|||
import 'uiExports/embeddableActions';
|
||||
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
import { ExitFullScreenButton } from 'ui/exit_full_screen';
|
||||
import uiRoutes from 'ui/routes';
|
||||
// @ts-ignore
|
||||
|
@ -39,7 +38,6 @@ export const setup = pluginInstance.setup(npSetup.core, {
|
|||
embeddable: npSetup.plugins.embeddable,
|
||||
inspector: npSetup.plugins.inspector,
|
||||
__LEGACY: {
|
||||
SavedObjectFinder,
|
||||
ExitFullScreenButton,
|
||||
},
|
||||
});
|
||||
|
@ -64,7 +62,6 @@ export const start = pluginInstance.start(npStart.core, {
|
|||
inspector: npStart.plugins.inspector,
|
||||
uiActions: npStart.plugins.uiActions,
|
||||
__LEGACY: {
|
||||
SavedObjectFinder,
|
||||
ExitFullScreenButton,
|
||||
onRenderComplete: (renderCompleteListener: () => void) => {
|
||||
if (rendered) {
|
||||
|
|
|
@ -38,6 +38,10 @@ import {
|
|||
ContactCardEmbeddableFactory,
|
||||
} from './embeddable_api';
|
||||
import { App } from './app';
|
||||
import {
|
||||
SavedObjectFinderProps,
|
||||
SavedObjectFinderUi,
|
||||
} from '../../../../../../../src/plugins/kibana_react/public/saved_objects';
|
||||
import {
|
||||
IEmbeddableStart,
|
||||
IEmbeddableSetup,
|
||||
|
@ -47,7 +51,6 @@ export interface SetupDependencies {
|
|||
embeddable: IEmbeddableSetup;
|
||||
inspector: InspectorSetupContract;
|
||||
__LEGACY: {
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
ExitFullScreenButton: React.ComponentType<any>;
|
||||
};
|
||||
}
|
||||
|
@ -57,7 +60,6 @@ interface StartDependencies {
|
|||
uiActions: IUiActionsStart;
|
||||
inspector: InspectorStartContract;
|
||||
__LEGACY: {
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
ExitFullScreenButton: React.ComponentType<any>;
|
||||
onRenderComplete: (onRenderComplete: () => void) => void;
|
||||
};
|
||||
|
@ -99,6 +101,13 @@ export class EmbeddableExplorerPublicPlugin
|
|||
|
||||
plugins.__LEGACY.onRenderComplete(() => {
|
||||
const root = document.getElementById(REACT_ROOT_ID);
|
||||
const SavedObjectFinder = (props: SavedObjectFinderProps) => (
|
||||
<SavedObjectFinderUi
|
||||
{...props}
|
||||
savedObjects={core.savedObjects}
|
||||
uiSettings={core.uiSettings}
|
||||
/>
|
||||
);
|
||||
ReactDOM.render(
|
||||
<App
|
||||
getActions={plugins.uiActions.getTriggerCompatibleActions}
|
||||
|
@ -107,7 +116,7 @@ export class EmbeddableExplorerPublicPlugin
|
|||
notifications={core.notifications}
|
||||
overlays={core.overlays}
|
||||
inspector={plugins.inspector}
|
||||
SavedObjectFinder={plugins.__LEGACY.SavedObjectFinder}
|
||||
SavedObjectFinder={SavedObjectFinder}
|
||||
I18nContext={core.i18n.Context}
|
||||
/>,
|
||||
root
|
||||
|
|
|
@ -16,8 +16,11 @@ import {
|
|||
} from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
|
||||
import { start } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
|
||||
import { EmbeddableExpression } from '../expression_types/embeddable';
|
||||
import { SavedObjectFinder } from '../../../../../../src/legacy/ui/public/saved_objects/components/saved_object_finder';
|
||||
import { RendererStrings } from '../../i18n';
|
||||
import {
|
||||
SavedObjectFinderProps,
|
||||
SavedObjectFinderUi,
|
||||
} from '../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
|
||||
|
@ -34,6 +37,13 @@ interface Handlers {
|
|||
}
|
||||
|
||||
const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) => {
|
||||
const SavedObjectFinder = (props: SavedObjectFinderProps) => (
|
||||
<SavedObjectFinderUi
|
||||
{...props}
|
||||
savedObjects={npStart.core.savedObjects}
|
||||
uiSettings={npStart.core.uiSettings}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="embeddable"
|
||||
|
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
|||
|
||||
import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui';
|
||||
import {
|
||||
SavedObjectFinder,
|
||||
SavedObjectFinderUi,
|
||||
SavedObjectMetaData,
|
||||
} from '../../../../../../../src/plugins/kibana_react/public/saved_objects'; // eslint-disable-line
|
||||
import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
|
||||
|
@ -64,7 +64,7 @@ export class AddEmbeddableFlyout extends React.Component<Props> {
|
|||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<SavedObjectFinder
|
||||
<SavedObjectFinderUi
|
||||
onChoose={this.onAddPanel}
|
||||
savedObjectMetaData={availableSavedObjects}
|
||||
showFilter={true}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
|
||||
import { CoreStart } from 'src/core/public';
|
||||
import { SavedObjectFinder } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { SavedObjectFinderUi } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { IndexPatternSavedObject } from '../types';
|
||||
|
||||
export interface SourcePickerProps {
|
||||
|
@ -25,7 +25,7 @@ export function SourcePicker({
|
|||
onIndexPatternSelected,
|
||||
}: SourcePickerProps) {
|
||||
return (
|
||||
<SavedObjectFinder
|
||||
<SavedObjectFinderUi
|
||||
savedObjects={savedObjects}
|
||||
uiSettings={uiSettings}
|
||||
onChoose={(_id, _type, _name, indexPattern) => {
|
||||
|
|
|
@ -29,7 +29,7 @@ export type GetLogEntryRateRequestPayload = rt.TypeOf<typeof getLogEntryRateRequ
|
|||
* response
|
||||
*/
|
||||
|
||||
export const logEntryRateAnomaly = rt.type({
|
||||
export const logEntryRateAnomalyRT = rt.type({
|
||||
actualLogEntryRate: rt.number,
|
||||
anomalyScore: rt.number,
|
||||
duration: rt.number,
|
||||
|
@ -39,22 +39,26 @@ export const logEntryRateAnomaly = rt.type({
|
|||
|
||||
export const logEntryRatePartitionRT = rt.type({
|
||||
analysisBucketCount: rt.number,
|
||||
anomalies: rt.array(logEntryRateAnomaly),
|
||||
anomalies: rt.array(logEntryRateAnomalyRT),
|
||||
averageActualLogEntryRate: rt.number,
|
||||
maximumAnomalyScore: rt.number,
|
||||
numberOfLogEntries: rt.number,
|
||||
partitionId: rt.string,
|
||||
});
|
||||
|
||||
export const logEntryRateHistogramBucket = rt.type({
|
||||
export type LogEntryRatePartition = rt.TypeOf<typeof logEntryRatePartitionRT>;
|
||||
|
||||
export const logEntryRateHistogramBucketRT = rt.type({
|
||||
partitions: rt.array(logEntryRatePartitionRT),
|
||||
startTime: rt.number,
|
||||
});
|
||||
|
||||
export type LogEntryRateHistogramBucket = rt.TypeOf<typeof logEntryRateHistogramBucketRT>;
|
||||
|
||||
export const getLogEntryRateSuccessReponsePayloadRT = rt.type({
|
||||
data: rt.type({
|
||||
bucketDuration: rt.number,
|
||||
histogramBuckets: rt.array(logEntryRateHistogramBucket),
|
||||
histogramBuckets: rt.array(logEntryRateHistogramBucketRT),
|
||||
totalNumberOfLogEntries: rt.number,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './indices';
|
||||
export * from './log_entry_rate_indices';
|
||||
|
|
|
@ -6,14 +6,24 @@
|
|||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export const LOG_ANALYSIS_VALIDATION_INDICES_PATH = '/api/infra/log_analysis/validation/indices';
|
||||
export const LOG_ANALYSIS_VALIDATE_INDICES_PATH =
|
||||
'/api/infra/log_analysis/validation/log_entry_rate_indices';
|
||||
|
||||
/**
|
||||
* Request types
|
||||
*/
|
||||
export const validationIndicesFieldSpecificationRT = rt.type({
|
||||
name: rt.string,
|
||||
validTypes: rt.array(rt.string),
|
||||
});
|
||||
|
||||
export type ValidationIndicesFieldSpecification = rt.TypeOf<
|
||||
typeof validationIndicesFieldSpecificationRT
|
||||
>;
|
||||
|
||||
export const validationIndicesRequestPayloadRT = rt.type({
|
||||
data: rt.type({
|
||||
timestampField: rt.string,
|
||||
fields: rt.array(validationIndicesFieldSpecificationRT),
|
||||
indices: rt.array(rt.string),
|
||||
}),
|
||||
});
|
|
@ -4,19 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { JobType } from './log_analysis';
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export const bucketSpan = 900000;
|
||||
|
||||
export const partitionField = 'event.dataset';
|
||||
|
||||
export const getJobIdPrefix = (spaceId: string, sourceId: string) =>
|
||||
`kibana-logs-ui-${spaceId}-${sourceId}-`;
|
||||
|
||||
export const getJobId = (spaceId: string, sourceId: string, jobType: JobType) =>
|
||||
export const getJobId = (spaceId: string, sourceId: string, jobType: string) =>
|
||||
`${getJobIdPrefix(spaceId, sourceId)}${jobType}`;
|
||||
|
||||
export const getDatafeedId = (spaceId: string, sourceId: string, jobType: JobType) =>
|
||||
export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) =>
|
||||
`datafeed-${getJobId(spaceId, sourceId, jobType)}`;
|
||||
|
||||
export const getAllModuleJobIds = (spaceId: string, sourceId: string) => [
|
||||
getJobId(spaceId, sourceId, 'log-entry-rate'),
|
||||
];
|
||||
export const jobSourceConfigurationRT = rt.type({
|
||||
indexPattern: rt.string,
|
||||
timestampField: rt.string,
|
||||
bucketSpan: rt.number,
|
||||
});
|
||||
|
||||
export type JobSourceConfiguration = rt.TypeOf<typeof jobSourceConfigurationRT>;
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import url from 'url';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { encode } from 'rison-node';
|
||||
import chrome from 'ui/chrome';
|
||||
import { QueryString } from 'ui/utils/query_string';
|
||||
import { encode } from 'rison-node';
|
||||
import { TimeRange } from '../../../../../common/http_api/shared/time_range';
|
||||
import url from 'url';
|
||||
|
||||
import { TimeRange } from '../../../../common/http_api/shared/time_range';
|
||||
|
||||
export const AnalyzeInMlButton: React.FunctionComponent<{
|
||||
jobId: string;
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './analyze_in_ml_button';
|
|
@ -6,11 +6,30 @@
|
|||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
import { jobSourceConfigurationRT } from '../../../../../common/log_analysis';
|
||||
|
||||
export const jobCustomSettingsRT = rt.partial({
|
||||
job_revision: rt.number,
|
||||
logs_source_config: rt.partial({
|
||||
indexPattern: rt.string,
|
||||
timestampField: rt.string,
|
||||
bucketSpan: rt.number,
|
||||
}),
|
||||
logs_source_config: rt.partial(jobSourceConfigurationRT.props),
|
||||
});
|
||||
|
||||
export const getMlCapabilitiesResponsePayloadRT = rt.type({
|
||||
capabilities: rt.type({
|
||||
canGetJobs: rt.boolean,
|
||||
canCreateJob: rt.boolean,
|
||||
canDeleteJob: rt.boolean,
|
||||
canOpenJob: rt.boolean,
|
||||
canCloseJob: rt.boolean,
|
||||
canForecastJob: rt.boolean,
|
||||
canGetDatafeeds: rt.boolean,
|
||||
canStartStopDatafeed: rt.boolean,
|
||||
canUpdateJob: rt.boolean,
|
||||
canUpdateDatafeed: rt.boolean,
|
||||
canPreviewDatafeed: rt.boolean,
|
||||
}),
|
||||
isPlatinumOrTrialLicense: rt.boolean,
|
||||
mlFeatureEnabledInSpace: rt.boolean,
|
||||
upgradeInProgress: rt.boolean,
|
||||
});
|
||||
|
||||
export type GetMlCapabilitiesResponsePayload = rt.TypeOf<typeof getMlCapabilitiesResponsePayloadRT>;
|
||||
|
|
|
@ -9,17 +9,22 @@ import { pipe } from 'fp-ts/lib/pipeable';
|
|||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { getAllModuleJobIds, getDatafeedId } from '../../../../../common/log_analysis';
|
||||
|
||||
import { getDatafeedId, getJobId } from '../../../../../common/log_analysis';
|
||||
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callDeleteJobs = async (spaceId: string, sourceId: string) => {
|
||||
export const callDeleteJobs = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
|
||||
const deleteJobsResponse = await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/ml/jobs/delete_jobs',
|
||||
body: JSON.stringify(
|
||||
deleteJobsRequestPayloadRT.encode({
|
||||
jobIds: getAllModuleJobIds(spaceId, sourceId),
|
||||
jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -42,15 +47,24 @@ export const callGetJobDeletionTasks = async () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const callStopDatafeed = async (spaceId: string, sourceId: string) => {
|
||||
export const callStopDatafeeds = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
|
||||
const stopDatafeedResponse = await kfetch({
|
||||
method: 'POST',
|
||||
pathname: `/api/ml/datafeeds/${getDatafeedId(spaceId, sourceId, 'log-entry-rate')}/_stop`,
|
||||
pathname: '/api/ml/jobs/stop_datafeeds',
|
||||
body: JSON.stringify(
|
||||
stopDatafeedsRequestPayloadRT.encode({
|
||||
datafeedIds: jobTypes.map(jobType => getDatafeedId(spaceId, sourceId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
return pipe(
|
||||
stopDatafeedResponsePayloadRT.decode(stopDatafeedResponse),
|
||||
stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse),
|
||||
fold(throwErrors(createPlainError), identity)
|
||||
);
|
||||
};
|
||||
|
@ -68,10 +82,19 @@ export const deleteJobsResponsePayloadRT = rt.record(
|
|||
})
|
||||
);
|
||||
|
||||
export type DeleteJobsResponsePayload = rt.TypeOf<typeof deleteJobsResponsePayloadRT>;
|
||||
|
||||
export const getJobDeletionTasksResponsePayloadRT = rt.type({
|
||||
jobIds: rt.array(rt.string),
|
||||
});
|
||||
|
||||
export const stopDatafeedResponsePayloadRT = rt.type({
|
||||
stopped: rt.boolean,
|
||||
export const stopDatafeedsRequestPayloadRT = rt.type({
|
||||
datafeedIds: rt.array(rt.string),
|
||||
});
|
||||
|
||||
export const stopDatafeedsResponsePayloadRT = rt.record(
|
||||
rt.string,
|
||||
rt.type({
|
||||
stopped: rt.boolean,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -12,15 +12,19 @@ import { kfetch } from 'ui/kfetch';
|
|||
|
||||
import { jobCustomSettingsRT } from './ml_api_types';
|
||||
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||
import { getAllModuleJobIds } from '../../../../../common/log_analysis';
|
||||
import { getJobId } from '../../../../../common/log_analysis';
|
||||
|
||||
export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => {
|
||||
export const callJobsSummaryAPI = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
const response = await kfetch({
|
||||
method: 'POST',
|
||||
pathname: '/api/ml/jobs/jobs_summary',
|
||||
body: JSON.stringify(
|
||||
fetchJobStatusRequestPayloadRT.encode({
|
||||
jobIds: getAllModuleJobIds(spaceId, sourceId),
|
||||
jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
|
|
@ -12,7 +12,6 @@ import { kfetch } from 'ui/kfetch';
|
|||
|
||||
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||
import { getJobIdPrefix } from '../../../../../common/log_analysis';
|
||||
import { jobCustomSettingsRT } from './ml_api_types';
|
||||
|
||||
export const callSetupMlModuleAPI = async (
|
||||
moduleId: string,
|
||||
|
@ -21,8 +20,8 @@ export const callSetupMlModuleAPI = async (
|
|||
spaceId: string,
|
||||
sourceId: string,
|
||||
indexPattern: string,
|
||||
timeField: string,
|
||||
bucketSpan: number
|
||||
jobOverrides: SetupMlModuleJobOverrides[] = [],
|
||||
datafeedOverrides: SetupMlModuleDatafeedOverrides[] = []
|
||||
) => {
|
||||
const response = await kfetch({
|
||||
method: 'POST',
|
||||
|
@ -34,25 +33,8 @@ export const callSetupMlModuleAPI = async (
|
|||
indexPatternName: indexPattern,
|
||||
prefix: getJobIdPrefix(spaceId, sourceId),
|
||||
startDatafeed: true,
|
||||
jobOverrides: [
|
||||
{
|
||||
job_id: 'log-entry-rate' as const,
|
||||
analysis_config: {
|
||||
bucket_span: `${bucketSpan}ms`,
|
||||
},
|
||||
data_description: {
|
||||
time_field: timeField,
|
||||
},
|
||||
custom_settings: {
|
||||
logs_source_config: {
|
||||
indexPattern,
|
||||
timestampField: timeField,
|
||||
bucketSpan,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
datafeedOverrides: [],
|
||||
jobOverrides,
|
||||
datafeedOverrides,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -68,23 +50,20 @@ const setupMlModuleTimeParamsRT = rt.partial({
|
|||
end: rt.number,
|
||||
});
|
||||
|
||||
const setupMlModuleLogEntryRateJobOverridesRT = rt.type({
|
||||
job_id: rt.literal('log-entry-rate'),
|
||||
analysis_config: rt.type({
|
||||
bucket_span: rt.string,
|
||||
}),
|
||||
data_description: rt.type({
|
||||
time_field: rt.string,
|
||||
}),
|
||||
custom_settings: jobCustomSettingsRT,
|
||||
});
|
||||
const setupMlModuleJobOverridesRT = rt.object;
|
||||
|
||||
export type SetupMlModuleJobOverrides = rt.TypeOf<typeof setupMlModuleJobOverridesRT>;
|
||||
|
||||
const setupMlModuleDatafeedOverridesRT = rt.object;
|
||||
|
||||
export type SetupMlModuleDatafeedOverrides = rt.TypeOf<typeof setupMlModuleDatafeedOverridesRT>;
|
||||
|
||||
const setupMlModuleRequestParamsRT = rt.type({
|
||||
indexPatternName: rt.string,
|
||||
prefix: rt.string,
|
||||
startDatafeed: rt.boolean,
|
||||
jobOverrides: rt.array(setupMlModuleLogEntryRateJobOverridesRT),
|
||||
datafeedOverrides: rt.array(rt.object),
|
||||
jobOverrides: rt.array(setupMlModuleJobOverridesRT),
|
||||
datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT),
|
||||
});
|
||||
|
||||
const setupMlModuleRequestPayloadRT = rt.intersection([
|
||||
|
|
|
@ -10,20 +10,22 @@ import { identity } from 'fp-ts/lib/function';
|
|||
import { kfetch } from 'ui/kfetch';
|
||||
|
||||
import {
|
||||
LOG_ANALYSIS_VALIDATION_INDICES_PATH,
|
||||
LOG_ANALYSIS_VALIDATE_INDICES_PATH,
|
||||
ValidationIndicesFieldSpecification,
|
||||
validationIndicesRequestPayloadRT,
|
||||
validationIndicesResponsePayloadRT,
|
||||
} from '../../../../../common/http_api';
|
||||
|
||||
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||
|
||||
export const callIndexPatternsValidate = async (timestampField: string, indices: string[]) => {
|
||||
export const callValidateIndicesAPI = async (
|
||||
indices: string[],
|
||||
fields: ValidationIndicesFieldSpecification[]
|
||||
) => {
|
||||
const response = await kfetch({
|
||||
method: 'POST',
|
||||
pathname: LOG_ANALYSIS_VALIDATION_INDICES_PATH,
|
||||
body: JSON.stringify(
|
||||
validationIndicesRequestPayloadRT.encode({ data: { timestampField, indices } })
|
||||
),
|
||||
pathname: LOG_ANALYSIS_VALIDATE_INDICES_PATH,
|
||||
body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })),
|
||||
});
|
||||
|
||||
return pipe(
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
export * from './log_analysis_capabilities';
|
||||
export * from './log_analysis_cleanup';
|
||||
export * from './log_analysis_jobs';
|
||||
export * from './log_analysis_results';
|
||||
export * from './log_analysis_results_url_state';
|
||||
export * from './log_analysis_status_state';
|
||||
export * from './log_analysis_module';
|
||||
export * from './log_analysis_module_status';
|
||||
export * from './log_analysis_module_types';
|
||||
|
|
|
@ -15,7 +15,7 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
|||
import {
|
||||
getMlCapabilitiesResponsePayloadRT,
|
||||
GetMlCapabilitiesResponsePayload,
|
||||
} from './ml_api_types';
|
||||
} from './api/ml_api_types';
|
||||
import { throwErrors, createPlainError } from '../../../../common/runtime_types';
|
||||
|
||||
export const useLogAnalysisCapabilities = () => {
|
||||
|
|
|
@ -4,64 +4,46 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import { useMemo } from 'react';
|
||||
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { callDeleteJobs, callStopDatafeed, callGetJobDeletionTasks } from './api/ml_cleanup';
|
||||
import { getAllModuleJobIds } from '../../../../common/log_analysis';
|
||||
import { getJobId } from '../../../../common/log_analysis';
|
||||
import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup';
|
||||
|
||||
export const useLogAnalysisCleanup = ({
|
||||
sourceId,
|
||||
spaceId,
|
||||
}: {
|
||||
sourceId: string;
|
||||
spaceId: string;
|
||||
}) => {
|
||||
const [cleanupMLResourcesRequest, cleanupMLResources] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
try {
|
||||
await callStopDatafeed(spaceId, sourceId);
|
||||
} catch (err) {
|
||||
// Datefeed has been deleted / doesn't exist, proceed with deleting jobs anyway
|
||||
if (err && err.res && err.res.status === 404) {
|
||||
return await deleteJobs(spaceId, sourceId);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
export const cleanUpJobsAndDatafeeds = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
try {
|
||||
await callStopDatafeeds(spaceId, sourceId, jobTypes);
|
||||
} catch (err) {
|
||||
// Proceed only if datafeed has been deleted or didn't exist in the first place
|
||||
if (err?.res?.status !== 404) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return await deleteJobs(spaceId, sourceId);
|
||||
},
|
||||
},
|
||||
[spaceId, sourceId]
|
||||
);
|
||||
|
||||
const isCleaningUp = useMemo(() => cleanupMLResourcesRequest.state === 'pending', [
|
||||
cleanupMLResourcesRequest.state,
|
||||
]);
|
||||
|
||||
return {
|
||||
cleanupMLResources,
|
||||
isCleaningUp,
|
||||
};
|
||||
return await deleteJobs(spaceId, sourceId, jobTypes);
|
||||
};
|
||||
|
||||
export const LogAnalysisCleanup = createContainer(useLogAnalysisCleanup);
|
||||
|
||||
const deleteJobs = async (spaceId: string, sourceId: string) => {
|
||||
const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId);
|
||||
await waitUntilJobsAreDeleted(spaceId, sourceId);
|
||||
const deleteJobs = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes);
|
||||
await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes);
|
||||
return deleteJobsResponse;
|
||||
};
|
||||
|
||||
const waitUntilJobsAreDeleted = async (spaceId: string, sourceId: string) => {
|
||||
const waitUntilJobsAreDeleted = async <JobType extends string>(
|
||||
spaceId: string,
|
||||
sourceId: string,
|
||||
jobTypes: JobType[]
|
||||
) => {
|
||||
const moduleJobIds = jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType));
|
||||
while (true) {
|
||||
const response = await callGetJobDeletionTasks();
|
||||
const jobIdsBeingDeleted = response.jobIds;
|
||||
const moduleJobIds = getAllModuleJobIds(spaceId, sourceId);
|
||||
const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks();
|
||||
const needToWait = jobIdsBeingDeleted.some(jobId => moduleJobIds.includes(jobId));
|
||||
|
||||
if (needToWait) {
|
||||
await timeout(1000);
|
||||
} else {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue