mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Merge remote-tracking branch 'origin/master' into feature/merge-code
This commit is contained in:
commit
dc8dfbf6bd
427 changed files with 8036 additions and 6988 deletions
|
@ -1,8 +1,5 @@
|
|||
[[release-notes]]
|
||||
= {kib} Release Notes
|
||||
++++
|
||||
<titleabbrev>Release Notes</titleabbrev>
|
||||
++++
|
||||
= Release Notes
|
||||
|
||||
[partintro]
|
||||
--
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
[role="xpack"]
|
||||
[[xpack-graph]]
|
||||
= Graphing Connections in Your Data
|
||||
++++
|
||||
<titleabbrev>Graph</titleabbrev>
|
||||
++++
|
||||
|
||||
[partintro]
|
||||
--
|
||||
|
@ -66,6 +63,7 @@ multi-node clusters and scales with your Elasticsearch deployment.
|
|||
Advanced options let you control how your data is sampled and summarized.
|
||||
You can also set timeouts to prevent graph queries from adversely
|
||||
affecting the cluster.
|
||||
|
||||
--
|
||||
|
||||
include::getting-started.asciidoc[]
|
||||
|
|
|
@ -41,9 +41,6 @@ working on big documents. Set this property to `false` to disable highlighting.
|
|||
`doc_table:hideTimeColumn`:: Hide the 'Time' column in Discover and in all Saved Searches on Dashboards.
|
||||
`search:includeFrozen`:: Will include {ref}/frozen-indices.html[frozen indices] in results if enabled. Searching through frozen indices
|
||||
might increase the search time.
|
||||
`courier:maxSegmentCount`:: Kibana splits requests in the Discover app into segments to limit the size of requests sent to
|
||||
the Elasticsearch cluster. This setting constrains the length of the segment list. Long segment lists can significantly
|
||||
increase request processing time.
|
||||
`courier:ignoreFilterIfFieldNotInIndex`:: Set this property to `true` to skip filters that apply to fields that don't exist in a visualization's index. Useful when dashboards consist of visualizations from multiple index patterns.
|
||||
`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] setting used for _msearch requests sent by Kibana. Set to 0 to disable this config and use the Elasticsearch default.
|
||||
`fields:popularLimit`:: This setting governs how many of the top most popular fields are shown.
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
[[release-highlights]]
|
||||
= {kib} Release Highlights
|
||||
++++
|
||||
<titleabbrev>Release Highlights</titleabbrev>
|
||||
++++
|
||||
= Release Highlights
|
||||
|
||||
[partintro]
|
||||
--
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
[role="xpack"]
|
||||
[[automating-report-generation]]
|
||||
== Automating Report Generation
|
||||
You can automatically generate reports with a watch, or by submitting
|
||||
You can automatically generate reports with {watcher}, or by submitting
|
||||
HTTP POST requests from a script.
|
||||
|
||||
To automatically generate reports with a watch, you need to configure
|
||||
{watcher} to trust the Kibana server’s certificate. For more information,
|
||||
{watcher} to trust the {kib} server’s certificate. For more information,
|
||||
see <<securing-reporting, Securing Reporting>>.
|
||||
|
||||
include::report-intervals.asciidoc[]
|
||||
|
@ -13,27 +13,28 @@ include::report-intervals.asciidoc[]
|
|||
To get the URL for triggering a report generation during a given time period:
|
||||
|
||||
. Load the saved object.
|
||||
. Use the time-picker to specify a relative or absolute time period.
|
||||
. Click *Reporting* in the Kibana toolbar.
|
||||
. Copy the displayed **Generation URL**.
|
||||
. Use the timepicker to specify a relative or absolute time period.
|
||||
. Click *Share* in the Kibana toolbar.
|
||||
. Select *PDF Reports*.
|
||||
. Click **Copy POST URL**.
|
||||
|
||||
NOTE: The response from this request with be JSON, and will contain a `path` property
|
||||
with a URL to use to download the generated report. When requesting that path,
|
||||
you will get a 503 response if it's not completed yet. In this case, retry after the
|
||||
number of seconds in the `Retry-After` header in the response until you get the PDF.
|
||||
number of seconds in the `Retry-After` header in the response until the PDF is returned.
|
||||
|
||||
To configure a watch to email reports, you use the `reporting` attachment type
|
||||
in an `email` action. For more information, see
|
||||
{xpack-ref}/actions-email.html#configuring-email[Configuring Email Accounts].
|
||||
{stack-ov}/actions-email.html#configuring-email[Configuring Email Accounts].
|
||||
|
||||
include::watch-example.asciidoc[]
|
||||
|
||||
For more information about configuring watches, see
|
||||
{xpack-ref}/how-watcher-works.html[How Watcher Works].
|
||||
{stack-ov}/how-watcher-works.html[How Watcher Works].
|
||||
|
||||
== Deprecated Report URLs
|
||||
|
||||
The following is deprecated in 6.0, and you should now use Kibana to get the URL for a
|
||||
The following is deprecated in 6.0, and you should now use {kib} to get the URL for a
|
||||
particular report.
|
||||
|
||||
You may request PDF reports optimized for printing through three {reporting} endpoints:
|
||||
|
|
|
@ -49,5 +49,4 @@ image:reporting/images/share-button.png["Reporting Button",link="share-button.pn
|
|||
=== Generating a Report Automatically
|
||||
|
||||
If you want to automatically generate reports from a script or with
|
||||
{watcher}, use the displayed Generation URL. For more information, see
|
||||
<<automating-report-generation, Automating Report Generation>>
|
||||
{watcher}, see <<automating-report-generation, Automating Report Generation>>
|
||||
|
|
|
@ -73,8 +73,7 @@ xpack.security.sessionTimeout: 600000
|
|||
|
||||
. Restart {kib}.
|
||||
|
||||
[[kibana-roles]]
|
||||
. Choose an authentication mechanism and grant users the privileges they need to
|
||||
. [[kibana-roles]]Choose an authentication mechanism and grant users the privileges they need to
|
||||
use {kib}.
|
||||
+
|
||||
--
|
||||
|
|
|
@ -96,7 +96,7 @@ Some example translations are shown here:
|
|||
`KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId`
|
||||
`XPACK_MONITORING_ENABLED`:: `xpack.monitoring.enabled`
|
||||
|
||||
In general, any setting listed in <<settings>> or <<settings-xpack-kb>> can be
|
||||
In general, any setting listed in <<settings>> can be
|
||||
configured with this technique.
|
||||
|
||||
These variables can be set with +docker-compose+ like this:
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
"@babel/polyfill": "^7.2.5",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@elastic/datemath": "5.0.2",
|
||||
"@elastic/eui": "9.5.0",
|
||||
"@elastic/eui": "9.7.1",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/good": "8.1.1-kibana2",
|
||||
"@elastic/numeral": "2.3.2",
|
||||
|
@ -295,6 +295,7 @@
|
|||
"@types/json5": "^0.0.30",
|
||||
"@types/listr": "^0.13.0",
|
||||
"@types/lodash": "^3.10.1",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"@types/minimatch": "^2.0.29",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/moment-timezone": "^0.5.8",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
"abort-controller": "^2.0.3",
|
||||
"chalk": "^2.4.1",
|
||||
"dedent": "^0.7.0",
|
||||
"del": "^3.0.0",
|
||||
|
|
286
packages/kbn-es/src/artifact.js
Normal file
286
packages/kbn-es/src/artifact.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* 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 fetch = require('node-fetch');
|
||||
const AbortController = require('abort-controller');
|
||||
const fs = require('fs');
|
||||
const { promisify } = require('util');
|
||||
const { pipeline, Transform } = require('stream');
|
||||
const mkdirp = require('mkdirp');
|
||||
const chalk = require('chalk');
|
||||
const { createHash } = require('crypto');
|
||||
const path = require('path');
|
||||
|
||||
const asyncPipeline = promisify(pipeline);
|
||||
const V1_VERSIONS_API = 'https://artifacts-api.elastic.co/v1/versions';
|
||||
|
||||
const { cache } = require('./utils');
|
||||
const { createCliError } = require('./errors');
|
||||
|
||||
const TEST_ES_SNAPSHOT_VERSION = process.env.TEST_ES_SNAPSHOT_VERSION
|
||||
? process.env.TEST_ES_SNAPSHOT_VERSION
|
||||
: 'latest';
|
||||
|
||||
function getChecksumType(checksumUrl) {
|
||||
if (checksumUrl.endsWith('.sha512')) {
|
||||
return 'sha512';
|
||||
}
|
||||
|
||||
throw new Error(`unable to determine checksum type: ${checksumUrl}`);
|
||||
}
|
||||
|
||||
function getPlatform(key) {
|
||||
if (key.includes('-linux-')) {
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
if (key.includes('-windows-')) {
|
||||
return 'win32';
|
||||
}
|
||||
|
||||
if (key.includes('-darwin-')) {
|
||||
return 'darwin';
|
||||
}
|
||||
}
|
||||
|
||||
exports.Artifact = class Artifact {
|
||||
/**
|
||||
* Fetch an Artifact from the Artifact API for a license level and version
|
||||
* @param {('oss'|'basic'|'trial')} license
|
||||
* @param {string} version
|
||||
* @param {ToolingLog} log
|
||||
*/
|
||||
static async get(license, version, log) {
|
||||
const urlVersion = `${encodeURIComponent(version)}-SNAPSHOT`;
|
||||
const urlBuild = encodeURIComponent(TEST_ES_SNAPSHOT_VERSION);
|
||||
const url = `${V1_VERSIONS_API}/${urlVersion}/builds/${urlBuild}/projects/elasticsearch`;
|
||||
|
||||
log.info('downloading artifact info from %s', chalk.bold(url));
|
||||
const abc = new AbortController();
|
||||
const resp = await fetch(url, { signal: abc.signal });
|
||||
const json = await resp.text();
|
||||
|
||||
if (resp.status === 404) {
|
||||
abc.abort();
|
||||
throw createCliError(
|
||||
`Snapshots for ${version}/${TEST_ES_SNAPSHOT_VERSION} are not available`
|
||||
);
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
abc.abort();
|
||||
throw new Error(`Unable to read artifact info from ${url}: ${resp.statusText}\n ${json}`);
|
||||
}
|
||||
|
||||
// parse the api response into an array of Artifact objects
|
||||
const {
|
||||
project: { packages: artifactInfoMap },
|
||||
} = JSON.parse(json);
|
||||
const filenames = Object.keys(artifactInfoMap);
|
||||
const hasNoJdkVersions = filenames.some(filename => filename.includes('-no-jdk-'));
|
||||
const artifactSpecs = filenames.map(filename => ({
|
||||
filename,
|
||||
url: artifactInfoMap[filename].url,
|
||||
checksumUrl: artifactInfoMap[filename].sha_url,
|
||||
checksumType: getChecksumType(artifactInfoMap[filename].sha_url),
|
||||
type: artifactInfoMap[filename].type,
|
||||
isOss: filename.includes('-oss-'),
|
||||
platform: getPlatform(filename),
|
||||
jdkRequired: hasNoJdkVersions ? filename.includes('-no-jdk-') : true,
|
||||
}));
|
||||
|
||||
// pick the artifact we are going to use for this license/version combo
|
||||
const reqOss = license === 'oss';
|
||||
const reqPlatform = artifactSpecs.some(a => a.platform !== undefined)
|
||||
? process.platform
|
||||
: undefined;
|
||||
const reqJdkRequired = hasNoJdkVersions ? false : true;
|
||||
const reqType = process.platform === 'win32' ? 'zip' : 'tar';
|
||||
|
||||
const artifactSpec = artifactSpecs.find(
|
||||
spec =>
|
||||
spec.isOss === reqOss &&
|
||||
spec.type === reqType &&
|
||||
spec.platform === reqPlatform &&
|
||||
spec.jdkRequired === reqJdkRequired
|
||||
);
|
||||
|
||||
if (!artifactSpec) {
|
||||
throw new Error(
|
||||
`Unable to determine artifact for license [${license}] and version [${version}]\n` +
|
||||
` options: ${filenames.join(',')}`
|
||||
);
|
||||
}
|
||||
|
||||
return new Artifact(artifactSpec, log);
|
||||
}
|
||||
|
||||
constructor(spec, log) {
|
||||
this._spec = spec;
|
||||
this._log = log;
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return this._spec.url;
|
||||
}
|
||||
|
||||
getChecksumUrl() {
|
||||
return this._spec.checksumUrl;
|
||||
}
|
||||
|
||||
getChecksumType() {
|
||||
return this._spec.checksumType;
|
||||
}
|
||||
|
||||
getFilename() {
|
||||
return this._spec.filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the artifact to disk, skips the download if the cache is
|
||||
* up-to-date, verifies checksum when downloaded
|
||||
* @param {string} dest
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async download(dest) {
|
||||
const cacheMeta = cache.readMeta(dest);
|
||||
const tmpPath = `${dest}.tmp`;
|
||||
|
||||
const artifactResp = await this._download(tmpPath, cacheMeta.etag, cacheMeta.ts);
|
||||
if (artifactResp.cached) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._verifyChecksum(artifactResp);
|
||||
|
||||
// cache the etag for future downloads
|
||||
cache.writeMeta(dest, { etag: artifactResp.etag });
|
||||
|
||||
// rename temp download to the destination location
|
||||
fs.renameSync(tmpPath, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the artifact with an etag
|
||||
* @param {string} tmpPath
|
||||
* @param {string} etag
|
||||
* @param {string} ts
|
||||
* @return {{ cached: true }|{ checksum: string, etag: string, first500Bytes: Buffer }}
|
||||
*/
|
||||
async _download(tmpPath, etag, ts) {
|
||||
const url = this.getUrl();
|
||||
|
||||
if (etag) {
|
||||
this._log.info('verifying cache of %s', chalk.bold(url));
|
||||
} else {
|
||||
this._log.info('downloading artifact from %s', chalk.bold(url));
|
||||
}
|
||||
|
||||
const abc = new AbortController();
|
||||
const resp = await fetch(url, {
|
||||
signal: abc.signal,
|
||||
headers: {
|
||||
'If-None-Match': etag,
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 304) {
|
||||
this._log.info('etags match, reusing cache from %s', chalk.bold(ts));
|
||||
|
||||
abc.abort();
|
||||
return {
|
||||
cached: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
abc.abort();
|
||||
throw new Error(`Unable to download elasticsearch snapshot: ${resp.statusText}`);
|
||||
}
|
||||
|
||||
if (etag) {
|
||||
this._log.info('cache invalid, redownloading');
|
||||
}
|
||||
|
||||
const hash = createHash(this.getChecksumType());
|
||||
let first500Bytes = Buffer.alloc(0);
|
||||
let contentLength = 0;
|
||||
|
||||
mkdirp.sync(path.dirname(tmpPath));
|
||||
await asyncPipeline(
|
||||
resp.body,
|
||||
new Transform({
|
||||
transform(chunk, encoding, cb) {
|
||||
contentLength += Buffer.byteLength(chunk);
|
||||
|
||||
if (first500Bytes.length < 500) {
|
||||
first500Bytes = Buffer.concat(
|
||||
[first500Bytes, chunk],
|
||||
first500Bytes.length + chunk.length
|
||||
).slice(0, 500);
|
||||
}
|
||||
|
||||
hash.update(chunk, encoding);
|
||||
cb(null, chunk);
|
||||
},
|
||||
}),
|
||||
fs.createWriteStream(tmpPath)
|
||||
);
|
||||
|
||||
return {
|
||||
checksum: hash.digest('hex'),
|
||||
etag: resp.headers.get('etag'),
|
||||
contentLength,
|
||||
first500Bytes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the checksum of the downloaded artifact with the checksum at checksumUrl
|
||||
* @param {{ checksum: string, contentLength: number, first500Bytes: Buffer }} artifactResp
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async _verifyChecksum(artifactResp) {
|
||||
this._log.info('downloading artifact checksum from %s', chalk.bold(this.getChecksumUrl()));
|
||||
|
||||
const abc = new AbortController();
|
||||
const resp = await fetch(this.getChecksumUrl(), {
|
||||
signal: abc.signal,
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
abc.abort();
|
||||
throw new Error(`Unable to download elasticsearch checksum: ${resp.statusText}`);
|
||||
}
|
||||
|
||||
// in format of stdout from `shasum` cmd, which is `<checksum> <filename>`
|
||||
const [expectedChecksum] = (await resp.text()).split(' ');
|
||||
if (artifactResp.checksum !== expectedChecksum) {
|
||||
const len = `${artifactResp.first500Bytes / artifactResp.contentLength}`;
|
||||
throw createCliError(
|
||||
`artifact downloaded from ${this.getUrl()} does not match expected checksum\n` +
|
||||
` expected: ${expectedChecksum}\n` +
|
||||
` received: ${artifactResp.checksum}\n` +
|
||||
` content[${len}]: ${artifactResp.first500Bytes.toString('utf8')}`
|
||||
);
|
||||
}
|
||||
|
||||
this._log.info('checksum verified');
|
||||
}
|
||||
};
|
|
@ -17,15 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const mkdirp = require('mkdirp');
|
||||
const chalk = require('chalk');
|
||||
const path = require('path');
|
||||
const { BASE_PATH } = require('../paths');
|
||||
const { installArchive } = require('./archive');
|
||||
const { log: defaultLog, cache } = require('../utils');
|
||||
const { log: defaultLog } = require('../utils');
|
||||
const { Artifact } = require('../artifact');
|
||||
|
||||
/**
|
||||
* Download an ES snapshot
|
||||
|
@ -44,15 +41,13 @@ exports.downloadSnapshot = async function installSnapshot({
|
|||
installPath = path.resolve(basePath, version),
|
||||
log = defaultLog,
|
||||
}) {
|
||||
const fileName = getFilename(license, version);
|
||||
const url = getUrl(fileName);
|
||||
const dest = path.resolve(basePath, 'cache', fileName);
|
||||
|
||||
log.info('version: %s', chalk.bold(version));
|
||||
log.info('install path: %s', chalk.bold(installPath));
|
||||
log.info('license: %s', chalk.bold(license));
|
||||
|
||||
await downloadFile(url, dest, log);
|
||||
const artifact = await Artifact.get(license, version, log);
|
||||
const dest = path.resolve(basePath, 'cache', artifact.getFilename());
|
||||
await artifact.download(dest);
|
||||
|
||||
return {
|
||||
downloadPath: dest,
|
||||
|
@ -94,83 +89,3 @@ exports.installSnapshot = async function installSnapshot({
|
|||
log,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloads to tmp and moves once complete
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {String} dest
|
||||
* @param {ToolingLog} log
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function downloadFile(url, dest, log) {
|
||||
const downloadPath = `${dest}.tmp`;
|
||||
const cacheMeta = cache.readMeta(dest);
|
||||
|
||||
mkdirp.sync(path.dirname(dest));
|
||||
|
||||
log.info('downloading from %s', chalk.bold(url));
|
||||
|
||||
return fetch(url, { headers: { 'If-None-Match': cacheMeta.etag } }).then(
|
||||
res =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (res.status === 304) {
|
||||
log.info('etags match, using cache from %s', chalk.bold(cacheMeta.ts));
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
return reject(new Error(`Unable to download elasticsearch snapshot: ${res.statusText}`));
|
||||
}
|
||||
|
||||
const stream = fs.createWriteStream(downloadPath);
|
||||
res.body
|
||||
.pipe(stream)
|
||||
.on('error', error => {
|
||||
reject(error);
|
||||
})
|
||||
.on('finish', () => {
|
||||
if (res.ok) {
|
||||
const etag = res.headers.get('etag');
|
||||
|
||||
cache.writeMeta(dest, { etag });
|
||||
fs.renameSync(downloadPath, dest);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(res.statusText));
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getFilename(license, version) {
|
||||
const platform = os.platform();
|
||||
let suffix = null;
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
suffix = 'darwin-x86_64.tar.gz';
|
||||
break;
|
||||
case 'linux':
|
||||
suffix = 'linux-x86_64.tar.gz';
|
||||
break;
|
||||
case 'win32':
|
||||
suffix = 'windows-x86_64.zip';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported platform ${platform}`);
|
||||
}
|
||||
|
||||
const basename = `elasticsearch${license === 'oss' ? '-oss-' : '-'}${version}`;
|
||||
return `${basename}-SNAPSHOT-${suffix}`;
|
||||
}
|
||||
|
||||
function getUrl(fileName) {
|
||||
if (process.env.TEST_ES_SNAPSHOT_VERSION) {
|
||||
return `https://snapshots.elastic.co/${
|
||||
process.env.TEST_ES_SNAPSHOT_VERSION
|
||||
}/downloads/elasticsearch/${fileName}`;
|
||||
} else {
|
||||
return `https://snapshots.elastic.co/downloads/elasticsearch/${fileName}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -921,7 +921,7 @@ describe('I18n engine', () => {
|
|||
await expect(i18n.load('some-url')).resolves.toBeUndefined();
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
expect(mockFetch).toHaveBeenCalledWith('some-url');
|
||||
expect(mockFetch).toHaveBeenCalledWith('some-url', { credentials: 'same-origin' });
|
||||
|
||||
expect(i18n.getTranslation()).toEqual(translations);
|
||||
});
|
||||
|
|
|
@ -240,7 +240,11 @@ export function init(newTranslation?: Translation) {
|
|||
* @param translationsUrl URL pointing to the JSON bundle with translations.
|
||||
*/
|
||||
export async function load(translationsUrl: string) {
|
||||
const response = await fetch(translationsUrl);
|
||||
// Once this package is integrated into core Kibana we should switch to an abstraction
|
||||
// around `fetch` provided by the platform, e.g. `kfetch`.
|
||||
const response = await fetch(translationsUrl, {
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
if (response.status >= 300) {
|
||||
throw new Error(`Translations request failed with status code: ${response.status}`);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"devDependencies": {
|
||||
"@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana",
|
||||
"@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana",
|
||||
"@kbn/expect": "1.0.0",
|
||||
"@kbn/expect": "link:../../packages/kbn-expect",
|
||||
"@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.14.1",
|
||||
|
|
|
@ -44,6 +44,7 @@ export default {
|
|||
'!packages/kbn-ui-framework/src/services/**/*/index.js',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^plugins/([^\/.]*)/(.*)': '<rootDir>/src/legacy/core_plugins/$1/public/$2',
|
||||
'^ui/(.*)': '<rootDir>/src/legacy/ui/public/$1',
|
||||
'^uiExports/(.*)': '<rootDir>/src/dev/jest/mocks/file_mock.js',
|
||||
'^test_utils/(.*)': '<rootDir>/src/test_utils/public/$1',
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import 'ui/doc_title';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import { applyResizeCheckerToEditors } from '../sense_editor_resize';
|
||||
import $ from 'jquery';
|
||||
import { initializeInput } from '../input';
|
||||
|
@ -34,7 +34,8 @@ module.run(function (Private, $rootScope) {
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('SenseController', function SenseController(Private, $scope, $timeout, $location, docTitle, kbnUiAceKeyboardModeService) {
|
||||
module.controller('SenseController', function SenseController(Private, $scope, $timeout, $location, kbnUiAceKeyboardModeService) {
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
docTitle.change('Console');
|
||||
|
||||
$scope.topNavController = Private(SenseTopNavController);
|
||||
|
|
|
@ -25,6 +25,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/input_control_vis/register_vis'
|
||||
],
|
||||
interpreter: ['plugins/input_control_vis/input_control_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const inputControlVis = () => ({
|
||||
|
@ -25,7 +26,7 @@ export const inputControlVis = () => ({
|
|||
context: {
|
||||
types: [],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.input_control.help', {
|
||||
help: i18n.translate('inputControl.function.help', {
|
||||
defaultMessage: 'Input control visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -46,3 +47,5 @@ export const inputControlVis = () => ({
|
|||
};
|
||||
}
|
||||
});
|
||||
|
||||
functionsRegistry.register(inputControlVis);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { inputControlVis } from './input_control';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { inputControlVis } from './input_control_fn';
|
||||
|
||||
describe('interpreter/functions#input_control_vis', () => {
|
||||
const fn = functionWrapper(inputControlVis);
|
|
@ -21,21 +21,8 @@ import { clog } from './clog';
|
|||
import { esaggs } from './esaggs';
|
||||
import { kibana } from './kibana';
|
||||
import { kibanaContext } from './kibana_context';
|
||||
import { vega } from './vega';
|
||||
import { timelionVis } from './timelion_vis';
|
||||
import { tsvb } from './tsvb';
|
||||
import { kibanaMarkdown } from './markdown';
|
||||
import { inputControlVis } from './input_control';
|
||||
import { metric } from './metric';
|
||||
import { kibanaPie } from './pie';
|
||||
import { regionmap } from './regionmap';
|
||||
import { tilemap } from './tilemap';
|
||||
import { kibanaTable } from './table';
|
||||
import { tagcloud } from './tagcloud';
|
||||
import { vislib } from './vislib';
|
||||
import { visualization } from './visualization';
|
||||
|
||||
export const functions = [
|
||||
clog, esaggs, kibana, kibanaContext, vega, timelionVis, tsvb, kibanaMarkdown, inputControlVis,
|
||||
metric, kibanaPie, regionmap, tilemap, kibanaTable, tagcloud, vislib, visualization
|
||||
clog, esaggs, kibana, kibanaContext, visualization
|
||||
];
|
||||
|
|
|
@ -24,6 +24,10 @@ export default function (kibana) {
|
|||
uiExports: {
|
||||
visTypes: [
|
||||
'plugins/kbn_vislib_vis_types/kbn_vislib_vis_types'
|
||||
],
|
||||
interpreter: [
|
||||
'plugins/kbn_vislib_vis_types/pie_fn',
|
||||
'plugins/kbn_vislib_vis_types/vislib_fn',
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
type="number"
|
||||
class="form-control"
|
||||
name="range.from"
|
||||
greater-or-equal-than="{{getGreaterThan($index)}}"
|
||||
greater-or-equal-than="getGreaterThan($index)"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
|
@ -184,7 +184,7 @@
|
|||
type="number"
|
||||
class="form-control"
|
||||
name="range.to"
|
||||
greater-or-equal-than="{{range.from}}"
|
||||
greater-or-equal-than="range.from"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
|
|
|
@ -81,7 +81,7 @@ module.directive('gaugeOptions', function (i18n) {
|
|||
};
|
||||
|
||||
$scope.getGreaterThan = function (index) {
|
||||
if (index === 0) return 0;
|
||||
if (index === 0) return -Infinity;
|
||||
return $scope.editorState.params.gauge.colorsRange[index - 1].to;
|
||||
};
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
type="number"
|
||||
class="form-control"
|
||||
name="range.from"
|
||||
greater-or-equal-than="{{getGreaterThan($index)}}"
|
||||
greater-or-equal-than="getGreaterThan($index)"
|
||||
step="any" />
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -64,7 +64,7 @@ module.directive('heatmapOptions', function (i18n) {
|
|||
};
|
||||
|
||||
$scope.getGreaterThan = function (index) {
|
||||
if (index === 0) return;
|
||||
if (index === 0) return -Infinity;
|
||||
return $scope.editorState.params.colorsRange[index - 1].to;
|
||||
};
|
||||
|
||||
|
|
|
@ -284,6 +284,7 @@
|
|||
class="kuiInput visEditorSidebar__input"
|
||||
type="number"
|
||||
step="0.1"
|
||||
greater-than="axis.scale.min"
|
||||
ng-model="axis.scale.max"
|
||||
>
|
||||
</div>
|
||||
|
@ -301,7 +302,8 @@
|
|||
class="kuiInput visEditorSidebar__input"
|
||||
type="number"
|
||||
step="0.1"
|
||||
greater-than="{{axis.scale.type === 'log' ? 0 : ''}}"
|
||||
greater-than="axis.scale.type === 'log' ? 0 : undefined"
|
||||
less-than="axis.scale.max"
|
||||
ng-model="axis.scale.min"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
class="form-control"
|
||||
type="number"
|
||||
step="0.1"
|
||||
greater-than="{{editorState.params.yAxis.min}}"
|
||||
greater-than="editorState.params.yAxis.min"
|
||||
ng-model="editorState.params.yAxis.max"
|
||||
ng-required="editorState.params.setYExtents">
|
||||
</label>
|
||||
|
@ -30,8 +30,8 @@
|
|||
class="form-control"
|
||||
type="number"
|
||||
step="0.1"
|
||||
less-than="{{editorState.params.yAxis.max}}"
|
||||
greater-than="{{editorState.params.scale === 'log' ? 0 : ''}}"
|
||||
less-than="editorState.params.yAxis.max"
|
||||
greater-than="editorState.params.scale === 'log' ? 0 : undefined"
|
||||
ng-model="editorState.params.yAxis.min"
|
||||
ng-required="editorState.params.setYExtents">
|
||||
</label>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { VislibSlicesResponseHandlerProvider as vislibSlicesResponseHandler } from 'ui/vis/response_handlers/vislib';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -28,7 +29,7 @@ export const kibanaPie = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.pie.help', {
|
||||
help: i18n.translate('kbnVislibVisTypes.functions.pie.help', {
|
||||
defaultMessage: 'Pie visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -57,3 +58,5 @@ export const kibanaPie = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(kibanaPie);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { kibanaPie } from './pie';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { kibanaPie } from './pie_fn';
|
||||
|
||||
const mockResponseHandler = jest.fn().mockReturnValue(Promise.resolve({
|
||||
hits: 1,
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
|
||||
import chrome from 'ui/chrome';
|
||||
|
@ -29,7 +30,7 @@ export const vislib = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.vislib.help', {
|
||||
help: i18n.translate('kbnVislibVisTypes.functions.vislib.help', {
|
||||
defaultMessage: 'Vislib visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -60,3 +61,5 @@ export const vislib = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(vislib);
|
|
@ -20,6 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
import 'ui/listen';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import chrome from 'ui/chrome';
|
|||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import 'ui/listen';
|
||||
import 'ui/search_bar';
|
||||
import 'ui/apply_filters';
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ export class DashboardStateManager {
|
|||
* Resets the state back to the last saved version of the dashboard.
|
||||
*/
|
||||
resetState() {
|
||||
// In order to show the correct warning for the saved-object-save-as-check-box we have to store the unsaved
|
||||
// In order to show the correct warning, we have to store the unsaved
|
||||
// title on the dashboard object. We should fix this at some point, but this is how all the other object
|
||||
// save panels work at the moment.
|
||||
this.savedDashboard.title = this.savedDashboard.lastSavedTitle;
|
||||
|
@ -341,9 +341,6 @@ export class DashboardStateManager {
|
|||
|
||||
setTitle(title) {
|
||||
this.appState.title = title;
|
||||
// The saved-object-save-as-check-box shows a warning if the current object title is different then
|
||||
// the existing object title. It calculates this difference by comparing this.dashboard.title to
|
||||
// this.dashboard.lastSavedTitle, so we need to push the temporary, unsaved title, onto the dashboard.
|
||||
this.savedDashboard.title = title;
|
||||
this.saveState();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import './saved_dashboard/saved_dashboards';
|
|||
import './dashboard_config';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import chrome from 'ui/chrome';
|
||||
import 'ui/filter_bar';
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
|
||||
import template from '../partials/dev_tools_app.html';
|
||||
import 'ui/kbn_top_nav';
|
||||
|
||||
uiModules
|
||||
.get('apps/dev_tools')
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import uiRoutes from 'ui/routes';
|
||||
import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import 'ui/directives/kbn_href';
|
||||
import './directives/dev_tools_app';
|
||||
|
||||
uiRoutes
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
import _ from 'lodash';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from '@kbn/expect';
|
||||
import PluginsKibanaDiscoverHitSortFnProvider from '../_hit_sort_fn';
|
||||
|
||||
describe('hit sort function', function () {
|
||||
let createHitSortFn;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
createHitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider);
|
||||
}));
|
||||
|
||||
|
||||
const runSortTest = function (dir, sortOpts) {
|
||||
const groupSize = _.random(10, 30);
|
||||
const total = sortOpts.length * groupSize;
|
||||
|
||||
sortOpts = sortOpts.map(function (opt) {
|
||||
if (Array.isArray(opt)) return opt;
|
||||
else return [opt];
|
||||
});
|
||||
const sortOptLength = sortOpts.length;
|
||||
|
||||
const hits = _.times(total, function (i) {
|
||||
return {
|
||||
_source: {},
|
||||
sort: sortOpts[i % sortOptLength]
|
||||
};
|
||||
});
|
||||
|
||||
hits.sort(createHitSortFn(dir))
|
||||
.forEach(function (hit, i) {
|
||||
const group = Math.floor(i / groupSize);
|
||||
expect(hit.sort).to.eql(sortOpts[group]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
it('sorts a list of hits in ascending order', function () {
|
||||
runSortTest('asc', [200, 404, 500]);
|
||||
});
|
||||
|
||||
it('sorts a list of hits in descending order', function () {
|
||||
runSortTest('desc', [10, 3, 1]);
|
||||
});
|
||||
|
||||
it('breaks ties in ascending order', function () {
|
||||
runSortTest('asc', [
|
||||
[ 'apache', 200, 'facebook.com' ],
|
||||
[ 'apache', 200, 'twitter.com' ],
|
||||
[ 'apache', 301, 'facebook.com' ],
|
||||
[ 'apache', 301, 'twitter.com' ],
|
||||
[ 'nginx', 200, 'facebook.com' ],
|
||||
[ 'nginx', 200, 'twitter.com' ],
|
||||
[ 'nginx', 301, 'facebook.com' ],
|
||||
[ 'nginx', 301, 'twitter.com' ]
|
||||
]);
|
||||
});
|
||||
|
||||
it('breaks ties in descending order', function () {
|
||||
runSortTest('desc', [
|
||||
[ 'nginx', 301, 'twitter.com' ],
|
||||
[ 'nginx', 301, 'facebook.com' ],
|
||||
[ 'nginx', 200, 'twitter.com' ],
|
||||
[ 'nginx', 200, 'facebook.com' ],
|
||||
[ 'apache', 301, 'twitter.com' ],
|
||||
[ 'apache', 301, 'facebook.com' ],
|
||||
[ 'apache', 200, 'twitter.com' ],
|
||||
[ 'apache', 200, 'facebook.com' ]
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,82 +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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-default-export
|
||||
export default function HitSortFnFactory() {
|
||||
/**
|
||||
* Creates a sort function that will resort hits based on the value
|
||||
* es used to sort them.
|
||||
*
|
||||
* background:
|
||||
* When a hit is sorted by elasticsearch, es will write the values that it used
|
||||
* to sort them into an array at the top level of the hit like so
|
||||
*
|
||||
* ```
|
||||
* hits: {
|
||||
* total: x,
|
||||
* hits: [
|
||||
* {
|
||||
* _id: i,
|
||||
* _source: {},
|
||||
* sort: [
|
||||
* // all values used to sort, in the order of precedence
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {[type]} field [description]
|
||||
* @param {[type]} direction [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
return function createHitSortFn(direction) {
|
||||
const descending = (direction === 'desc');
|
||||
|
||||
return function sortHits(hitA, hitB) {
|
||||
let bBelowa = null;
|
||||
|
||||
const aSorts = hitA.sort || [];
|
||||
const bSorts = hitB.sort || [];
|
||||
|
||||
// walk each sort value, and compare until one is different
|
||||
for (let i = 0; i < bSorts.length; i++) {
|
||||
const a = aSorts[i];
|
||||
const b = bSorts[i];
|
||||
|
||||
if (a == null || b > a) {
|
||||
bBelowa = !descending;
|
||||
break;
|
||||
}
|
||||
|
||||
if (b < a) {
|
||||
bBelowa = descending;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bBelowa !== null) {
|
||||
return bBelowa ? -1 : 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}
|
|
@ -26,9 +26,9 @@ import * as columnActions from 'ui/doc_table/actions/columns';
|
|||
import * as filterActions from 'ui/doc_table/actions/filter';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import 'ui/doc_table';
|
||||
import 'ui/listen';
|
||||
import 'ui/visualize';
|
||||
import 'ui/fixed_scroll';
|
||||
import 'ui/directives/validate_json';
|
||||
import 'ui/filters/moment';
|
||||
import 'ui/index_patterns';
|
||||
import 'ui/state_management/app_state';
|
||||
|
@ -39,7 +39,6 @@ import { toastNotifications } from 'ui/notify';
|
|||
import { VisProvider } from 'ui/vis';
|
||||
import { VislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import PluginsKibanaDiscoverHitSortFnProvider from '../_hit_sort_fn';
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
import { intervalOptions } from 'ui/agg_types/buckets/_interval_options';
|
||||
import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory';
|
||||
|
@ -67,6 +66,12 @@ import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_s
|
|||
import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs';
|
||||
import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline';
|
||||
|
||||
const fetchStatuses = {
|
||||
UNINITIALIZED: 'uninitialized',
|
||||
LOADING: 'loading',
|
||||
COMPLETE: 'complete',
|
||||
};
|
||||
|
||||
const app = uiModules.get('apps/discover', [
|
||||
'kibana/notify',
|
||||
'kibana/courier',
|
||||
|
@ -170,7 +175,6 @@ function discoverController(
|
|||
let visualizeHandler;
|
||||
const Vis = Private(VisProvider);
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider);
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
const responseHandler = Private(VislibSeriesResponseHandlerProvider).handler;
|
||||
const filterManager = Private(FilterManagerProvider);
|
||||
|
@ -190,6 +194,7 @@ function discoverController(
|
|||
$scope.intervalOptions = intervalOptions;
|
||||
$scope.showInterval = false;
|
||||
$scope.minimumVisibleRows = 50;
|
||||
$scope.fetchStatus = fetchStatuses.UNINITIALIZED;
|
||||
|
||||
$scope.intervalEnabled = function (interval) {
|
||||
return interval.val !== 'custom';
|
||||
|
@ -373,18 +378,16 @@ function discoverController(
|
|||
const getFieldCounts = async () => {
|
||||
// the field counts aren't set until we have the data back,
|
||||
// so we wait for the fetch to be done before proceeding
|
||||
if (!$scope.fetchStatus) {
|
||||
if ($scope.fetchStatus === fetchStatuses.COMPLETE) {
|
||||
return $scope.fieldCounts;
|
||||
}
|
||||
|
||||
return await new Promise(resolve => {
|
||||
const unwatch = $scope.$watch('fetchStatus', (newValue) => {
|
||||
if (newValue) {
|
||||
return;
|
||||
if (newValue === fetchStatuses.COMPLETE) {
|
||||
unwatch();
|
||||
resolve($scope.fieldCounts);
|
||||
}
|
||||
|
||||
unwatch();
|
||||
resolve($scope.fieldCounts);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -567,13 +570,9 @@ function discoverController(
|
|||
if (rows == null && oldRows == null) return status.LOADING;
|
||||
|
||||
const rowsEmpty = _.isEmpty(rows);
|
||||
// An undefined fetchStatus means the requests are still being
|
||||
// prepared to be sent. When all requests are completed,
|
||||
// fetchStatus is set to null, so it's important that we
|
||||
// specifically check for undefined to determine a loading status.
|
||||
const preparingForFetch = _.isUndefined(fetchStatus);
|
||||
const preparingForFetch = fetchStatus === fetchStatuses.UNINITIALIZED;
|
||||
if (preparingForFetch) return status.LOADING;
|
||||
else if (rowsEmpty && fetchStatus) return status.LOADING;
|
||||
else if (rowsEmpty && fetchStatus === fetchStatuses.LOADING) return status.LOADING;
|
||||
else if (!rowsEmpty) return status.READY;
|
||||
else return status.NO_RESULTS;
|
||||
}
|
||||
|
@ -662,6 +661,8 @@ function discoverController(
|
|||
.then(setupVisualization)
|
||||
.then(function () {
|
||||
$state.save();
|
||||
$scope.fetchStatus = fetchStatuses.LOADING;
|
||||
logInspectorRequest();
|
||||
return courier.fetch();
|
||||
})
|
||||
.catch(notify.error);
|
||||
|
@ -673,176 +674,72 @@ function discoverController(
|
|||
$scope.fetch();
|
||||
};
|
||||
|
||||
function onResults(resp) {
|
||||
logInspectorResponse(resp);
|
||||
|
||||
function handleSegmentedFetch(segmented) {
|
||||
function flushResponseData() {
|
||||
$scope.fetchError = undefined;
|
||||
$scope.hits = 0;
|
||||
$scope.failures = [];
|
||||
$scope.rows = [];
|
||||
$scope.fieldCounts = {};
|
||||
}
|
||||
|
||||
if (!$scope.rows) flushResponseData();
|
||||
|
||||
const sort = $state.sort;
|
||||
const timeField = $scope.indexPattern.timeFieldName;
|
||||
|
||||
/**
|
||||
* Basically an emum.
|
||||
*
|
||||
* opts:
|
||||
* "time" - sorted by the timefield
|
||||
* "non-time" - explicitly sorted by a non-time field, NOT THE SAME AS `sortBy !== "time"`
|
||||
* "implicit" - no sorting set, NOT THE SAME AS "non-time"
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
const sortBy = (function () {
|
||||
if (!Array.isArray(sort)) return 'implicit';
|
||||
else if (sort[0] === '_score') return 'implicit';
|
||||
else if (sort[0] === timeField) return 'time';
|
||||
else return 'non-time';
|
||||
}());
|
||||
|
||||
let sortFn = null;
|
||||
if (sortBy !== 'implicit') {
|
||||
sortFn = new HitSortFn(sort[1]);
|
||||
}
|
||||
|
||||
$scope.updateTime();
|
||||
|
||||
if (sort[0] === '_score') {
|
||||
segmented.setMaxSegments(1);
|
||||
}
|
||||
|
||||
segmented.setDirection(sortBy === 'time' ? (sort[1] || 'desc') : 'desc');
|
||||
segmented.setSortFn(sortFn);
|
||||
segmented.setSize($scope.opts.sampleSize);
|
||||
|
||||
let inspectorRequests = [];
|
||||
function logResponseInInspector(resp) {
|
||||
if (inspectorRequests.length > 0) {
|
||||
const inspectorRequest = inspectorRequests.shift();
|
||||
inspectorRequest
|
||||
.stats(getResponseInspectorStats($scope.searchSource, resp))
|
||||
.ok({ json: resp });
|
||||
}
|
||||
}
|
||||
|
||||
// triggered when the status updated
|
||||
segmented.on('status', function (status) {
|
||||
$scope.fetchStatus = status;
|
||||
if (status.complete === 0) {
|
||||
// starting new segmented search request
|
||||
inspectorAdapters.requests.reset();
|
||||
inspectorRequests = [];
|
||||
}
|
||||
|
||||
if (status.remaining > 0) {
|
||||
const inspectorRequest = inspectorAdapters.requests.start(
|
||||
i18n('kbn.discover.inspectorRequest.segmentFetchCompleteStatusTitle', {
|
||||
defaultMessage: 'Segment {fetchCompleteStatus}',
|
||||
values: {
|
||||
fetchCompleteStatus: $scope.fetchStatus.complete,
|
||||
if ($scope.opts.timefield) {
|
||||
const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp);
|
||||
$scope.searchSource.rawResponse = resp;
|
||||
Promise
|
||||
.resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource }))
|
||||
.then(resp => responseHandler(tabifiedData, resp))
|
||||
.then(resp => {
|
||||
visualizeHandler.render({
|
||||
as: 'visualization',
|
||||
value: {
|
||||
visType: $scope.vis.type.name,
|
||||
visData: resp,
|
||||
visConfig: $scope.vis.params,
|
||||
params: {},
|
||||
}
|
||||
}),
|
||||
{
|
||||
description: i18n('kbn.discover.inspectorRequest.segmentFetchCompleteStatusDescription', {
|
||||
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
|
||||
}),
|
||||
});
|
||||
inspectorRequest.stats(getRequestInspectorStats($scope.searchSource));
|
||||
$scope.searchSource.getSearchRequestBody().then(body => {
|
||||
inspectorRequest.json(body);
|
||||
});
|
||||
inspectorRequests.push(inspectorRequest);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
$scope.hits = resp.hits.total;
|
||||
$scope.rows = resp.hits.hits;
|
||||
|
||||
segmented.on('first', function () {
|
||||
flushResponseData();
|
||||
});
|
||||
// if we haven't counted yet, reset the counts
|
||||
const counts = $scope.fieldCounts = $scope.fieldCounts || {};
|
||||
|
||||
segmented.on('segment', (resp) => {
|
||||
logResponseInInspector(resp);
|
||||
if (resp._shards.failed > 0) {
|
||||
$scope.failures = _.union($scope.failures, resp._shards.failures);
|
||||
$scope.failures = _.uniq($scope.failures, false, function (failure) {
|
||||
return failure.index + failure.shard + failure.reason;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
segmented.on('emptySegment', function (resp) {
|
||||
logResponseInInspector(resp);
|
||||
});
|
||||
|
||||
segmented.on('mergedSegment', function (merged) {
|
||||
$scope.mergedEsResp = merged;
|
||||
|
||||
if ($scope.opts.timefield) {
|
||||
const tabifiedData = tabifyAggResponse($scope.vis.aggs, merged);
|
||||
$scope.searchSource.rawResponse = merged;
|
||||
Promise
|
||||
.resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource }))
|
||||
.then(resp => responseHandler(tabifiedData, resp))
|
||||
.then(resp => {
|
||||
visualizeHandler.render({
|
||||
as: 'visualization',
|
||||
value: {
|
||||
visType: $scope.vis.type.name,
|
||||
visData: resp,
|
||||
visConfig: $scope.vis.params,
|
||||
params: {},
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.hits = merged.hits.total;
|
||||
|
||||
const indexPattern = $scope.searchSource.getField('index');
|
||||
|
||||
// the merge rows, use a new array to help watchers
|
||||
$scope.rows = merged.hits.hits.slice();
|
||||
|
||||
let counts = $scope.fieldCounts;
|
||||
|
||||
// if we haven't counted yet, or need a fresh count because we are sorting, reset the counts
|
||||
if (!counts || sortFn) counts = $scope.fieldCounts = {};
|
||||
|
||||
$scope.rows.forEach(function (hit) {
|
||||
// skip this work if we have already done it
|
||||
if (hit.$$_counted) return;
|
||||
|
||||
// when we are sorting results, we need to redo the counts each time because the
|
||||
// "top 500" may change with each response, so don't mark this as counted
|
||||
if (!sortFn) hit.$$_counted = true;
|
||||
|
||||
const fields = _.keys(indexPattern.flattenHit(hit));
|
||||
let n = fields.length;
|
||||
let field;
|
||||
while (field = fields[--n]) {
|
||||
if (counts[field]) counts[field] += 1;
|
||||
else counts[field] = 1;
|
||||
}
|
||||
$scope.rows.forEach(hit => {
|
||||
const fields = Object.keys($scope.indexPattern.flattenHit(hit));
|
||||
fields.forEach(fieldName => {
|
||||
counts[fieldName] = (counts[fieldName] || 0) + 1;
|
||||
});
|
||||
});
|
||||
|
||||
segmented.on('complete', function () {
|
||||
if ($scope.fetchStatus.hitCount === 0) {
|
||||
flushResponseData();
|
||||
}
|
||||
$scope.fetchStatus = fetchStatuses.COMPLETE;
|
||||
|
||||
$scope.fetchStatus = null;
|
||||
return $scope.searchSource.onResults().then(onResults);
|
||||
}
|
||||
|
||||
let inspectorRequest;
|
||||
|
||||
function logInspectorRequest() {
|
||||
inspectorAdapters.requests.reset();
|
||||
const title = i18n('kbn.discover.inspectorRequestDataTitle', {
|
||||
defaultMessage: 'Data',
|
||||
});
|
||||
const description = i18n('kbn.discover.inspectorRequestDescription', {
|
||||
defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.',
|
||||
});
|
||||
inspectorRequest = inspectorAdapters.requests.start(title, { description });
|
||||
inspectorRequest.stats(getRequestInspectorStats($scope.searchSource));
|
||||
$scope.searchSource.getSearchRequestBody().then(body => {
|
||||
inspectorRequest.json(body);
|
||||
});
|
||||
}
|
||||
|
||||
function logInspectorResponse(resp) {
|
||||
inspectorRequest
|
||||
.stats(getResponseInspectorStats($scope.searchSource, resp))
|
||||
.ok({ json: resp });
|
||||
}
|
||||
|
||||
function beginSegmentedFetch() {
|
||||
$scope.searchSource.onBeginSegmentedFetch(handleSegmentedFetch)
|
||||
function startSearching() {
|
||||
return $scope.searchSource.onResults()
|
||||
.then(onResults)
|
||||
.catch((error) => {
|
||||
const fetchError = getPainlessError(error);
|
||||
|
||||
|
@ -853,10 +750,11 @@ function discoverController(
|
|||
}
|
||||
|
||||
// Restart. This enables auto-refresh functionality.
|
||||
beginSegmentedFetch();
|
||||
startSearching();
|
||||
});
|
||||
}
|
||||
beginSegmentedFetch();
|
||||
|
||||
startSearching();
|
||||
|
||||
$scope.updateTime = function () {
|
||||
$scope.timeRange = {
|
||||
|
|
|
@ -101,8 +101,6 @@
|
|||
></h2>
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
<div class="euiLoadingSpinner euiLoadingSpinner--large"></div>
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
<div ng-show="fetchStatus">{{fetchStatus.complete}} / {{fetchStatus.total}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -47,9 +47,16 @@ exports[`apmUiEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="APM automatically collects in-depth performance metrics and errors from inside your applications."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addAmpButtonLabel"
|
||||
>
|
||||
APM automatically collects in-depth performance metrics and errors from inside your applications.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addAmpButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -82,9 +89,16 @@ exports[`apmUiEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Ingest logs from popular data sources and easily visualize in preconfigured dashboards."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addLogDataButtonLabel"
|
||||
>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addLogDataButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -117,9 +131,16 @@ exports[`apmUiEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Collect metrics from the operating system and services running on your servers."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addMetricsButtonLabel"
|
||||
>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addMetricsButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -152,9 +173,16 @@ exports[`apmUiEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Centralize security events for interactive investigation in ready-to-go visualizations."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addSecurityButtonLabel"
|
||||
>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addSecurityButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -316,9 +344,16 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Ingest logs from popular data sources and easily visualize in preconfigured dashboards."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addLogDataButtonLabel"
|
||||
>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addLogDataButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -351,9 +386,16 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Collect metrics from the operating system and services running on your servers."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addMetricsButtonLabel"
|
||||
>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addMetricsButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -386,9 +428,16 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Centralize security events for interactive investigation in ready-to-go visualizations."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addSecurityButtonLabel"
|
||||
>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addSecurityButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -550,9 +599,16 @@ exports[`mlEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="APM automatically collects in-depth performance metrics and errors from inside your applications."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addAmpButtonLabel"
|
||||
>
|
||||
APM automatically collects in-depth performance metrics and errors from inside your applications.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addAmpButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -585,9 +641,16 @@ exports[`mlEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Ingest logs from popular data sources and easily visualize in preconfigured dashboards."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addLogDataButtonLabel"
|
||||
>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addLogDataButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -620,9 +683,16 @@ exports[`mlEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Collect metrics from the operating system and services running on your servers."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addMetricsButtonLabel"
|
||||
>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addMetricsButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -655,9 +725,16 @@ exports[`mlEnabled 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Centralize security events for interactive investigation in ready-to-go visualizations."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addSecurityButtonLabel"
|
||||
>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addSecurityButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -858,9 +935,16 @@ exports[`render 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Ingest logs from popular data sources and easily visualize in preconfigured dashboards."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addLogDataButtonLabel"
|
||||
>
|
||||
Ingest logs from popular data sources and easily visualize in preconfigured dashboards.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addLogDataButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -893,9 +977,16 @@ exports[`render 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Collect metrics from the operating system and services running on your servers."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addMetricsButtonLabel"
|
||||
>
|
||||
Collect metrics from the operating system and services running on your servers.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addMetricsButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
@ -928,9 +1019,16 @@ exports[`render 1`] = `
|
|||
>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
description="Centralize security events for interactive investigation in ready-to-go visualizations."
|
||||
description={
|
||||
<span
|
||||
id="aria-describedby.addSecurityButtonLabel"
|
||||
>
|
||||
Centralize security events for interactive investigation in ready-to-go visualizations.
|
||||
</span>
|
||||
}
|
||||
footer={
|
||||
<EuiButton
|
||||
aria-describedby="aria-describedby.addSecurityButtonLabel"
|
||||
className="homAddData__button"
|
||||
color="primary"
|
||||
fill={false}
|
||||
|
|
|
@ -44,46 +44,59 @@ const basePath = chrome.getBasePath();
|
|||
const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
||||
|
||||
const renderCards = () => {
|
||||
const apmTitle = intl.formatMessage({
|
||||
id: 'kbn.home.addData.apm.nameTitle', defaultMessage: 'APM'
|
||||
});
|
||||
const apmDescription = intl.formatMessage({
|
||||
id: 'kbn.home.addData.apm.nameDescription',
|
||||
defaultMessage: 'APM automatically collects in-depth performance metrics and errors from inside your applications.'
|
||||
});
|
||||
const loggingTitle = intl.formatMessage({
|
||||
id: 'kbn.home.addData.logging.nameTitle', defaultMessage: 'Logging'
|
||||
});
|
||||
const loggingDescription = intl.formatMessage({
|
||||
id: 'kbn.home.addData.logging.nameDescription',
|
||||
defaultMessage: 'Ingest logs from popular data sources and easily visualize in preconfigured dashboards.'
|
||||
});
|
||||
const metricsTitle = intl.formatMessage({
|
||||
id: 'kbn.home.addData.metrics.nameTitle', defaultMessage: 'Metrics'
|
||||
});
|
||||
const metricsDescription = intl.formatMessage({
|
||||
id: 'kbn.home.addData.metrics.nameDescription',
|
||||
defaultMessage: 'Collect metrics from the operating system and services running on your servers.'
|
||||
});
|
||||
const securityTitle = intl.formatMessage({
|
||||
id: 'kbn.home.addData.security.nameTitle', defaultMessage: 'Security analytics'
|
||||
});
|
||||
const securityDescription = intl.formatMessage({
|
||||
id: 'kbn.home.addData.security.nameDescription',
|
||||
defaultMessage: 'Centralize security events for interactive investigation in ready-to-go visualizations.'
|
||||
});
|
||||
const ampData = {
|
||||
title: intl.formatMessage({
|
||||
id: 'kbn.home.addData.apm.nameTitle', defaultMessage: 'APM'
|
||||
}),
|
||||
description: intl.formatMessage({
|
||||
id: 'kbn.home.addData.apm.nameDescription',
|
||||
defaultMessage: 'APM automatically collects in-depth performance metrics and errors from inside your applications.'
|
||||
}),
|
||||
ariaDescribedby: 'aria-describedby.addAmpButtonLabel'
|
||||
};
|
||||
const loggingData = {
|
||||
title: intl.formatMessage({
|
||||
id: 'kbn.home.addData.logging.nameTitle', defaultMessage: 'Logging'
|
||||
}),
|
||||
description: intl.formatMessage({
|
||||
id: 'kbn.home.addData.logging.nameDescription',
|
||||
defaultMessage: 'Ingest logs from popular data sources and easily visualize in preconfigured dashboards.'
|
||||
}),
|
||||
ariaDescribedby: 'aria-describedby.addLogDataButtonLabel'
|
||||
};
|
||||
const metricsData = {
|
||||
title: intl.formatMessage({
|
||||
id: 'kbn.home.addData.metrics.nameTitle', defaultMessage: 'Metrics'
|
||||
}),
|
||||
description: intl.formatMessage({
|
||||
id: 'kbn.home.addData.metrics.nameDescription',
|
||||
defaultMessage: 'Collect metrics from the operating system and services running on your servers.'
|
||||
}),
|
||||
ariaDescribedby: 'aria-describedby.addMetricsButtonLabel'
|
||||
};
|
||||
const securityData = {
|
||||
title: intl.formatMessage({
|
||||
id: 'kbn.home.addData.security.nameTitle', defaultMessage: 'Security analytics'
|
||||
}),
|
||||
description: intl.formatMessage({
|
||||
id: 'kbn.home.addData.security.nameDescription',
|
||||
defaultMessage: 'Centralize security events for interactive investigation in ready-to-go visualizations.'
|
||||
}),
|
||||
ariaDescribedby: 'aria-describedby.addSecurityButtonLabel'
|
||||
};
|
||||
|
||||
const getApmCard = () => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCard
|
||||
className="homAddData__card"
|
||||
icon={<EuiIcon className="homAddData__icon" type="apmApp" />}
|
||||
title={apmTitle}
|
||||
description={apmDescription}
|
||||
title={ampData.title}
|
||||
description={<span id={ampData.ariaDescribedby}>{ampData.description}</span>}
|
||||
footer={
|
||||
<EuiButton
|
||||
className="homAddData__button"
|
||||
href="#/home/tutorial/apm"
|
||||
aria-describedby={ampData.ariaDescribedby}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.home.addData.apm.addApmButtonLabel"
|
||||
|
@ -104,12 +117,13 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
|||
<EuiCard
|
||||
className="homAddData__card"
|
||||
icon={<EuiIcon className="homAddData__icon" type="loggingApp" />}
|
||||
title={loggingTitle}
|
||||
description={loggingDescription}
|
||||
title={loggingData.title}
|
||||
description={<span id={loggingData.ariaDescribedby}>{loggingData.description}</span>}
|
||||
footer={
|
||||
<EuiButton
|
||||
className="homAddData__button"
|
||||
href="#/home/tutorial_directory/logging"
|
||||
aria-describedby={loggingData.ariaDescribedby}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.home.addData.logging.addLogDataButtonLabel"
|
||||
|
@ -124,12 +138,13 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
|||
<EuiCard
|
||||
className="homAddData__card"
|
||||
icon={<EuiIcon className="homAddData__icon" type="monitoringApp" />}
|
||||
title={metricsTitle}
|
||||
description={metricsDescription}
|
||||
title={metricsData.title}
|
||||
description={<span id={metricsData.ariaDescribedby}>{metricsData.description}</span>}
|
||||
footer={
|
||||
<EuiButton
|
||||
className="homAddData__button"
|
||||
href="#/home/tutorial_directory/metrics"
|
||||
aria-describedby={metricsData.ariaDescribedby}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.home.addData.metrics.addMetricsDataButtonLabel"
|
||||
|
@ -144,12 +159,13 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
|||
<EuiCard
|
||||
className="homAddData__card"
|
||||
icon={<EuiIcon className="homAddData__icon" type="securityApp" />}
|
||||
title={securityTitle}
|
||||
description={securityDescription}
|
||||
title={securityData.title}
|
||||
description={<span id={securityData.ariaDescribedby}>{securityData.description}</span>}
|
||||
footer={
|
||||
<EuiButton
|
||||
className="homAddData__button"
|
||||
href="#/home/tutorial_directory/security"
|
||||
aria-describedby={securityData.ariaDescribedby}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.home.addData.security.addSecurityEventsButtonLabel"
|
||||
|
|
|
@ -47,6 +47,7 @@ import 'uiExports/autocompleteProviders';
|
|||
import 'uiExports/shareContextMenuExtensions';
|
||||
|
||||
import 'ui/autoload/all';
|
||||
import 'ui/kbn_top_nav';
|
||||
import './home';
|
||||
import './discover';
|
||||
import './visualize';
|
||||
|
|
|
@ -33,7 +33,6 @@ import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
|||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { EuiPageContent, EuiTitle, EuiText, EuiSpacer, EuiIcon, EuiHorizontalRule } from '@elastic/eui';
|
||||
import 'ui/kbn_top_nav';
|
||||
|
||||
const SIDENAV_ID = 'management-sidenav';
|
||||
const LANDING_ID = 'management-landing';
|
||||
|
|
|
@ -6,7 +6,7 @@ This is meant to serve as a guide to this area of code.
|
|||
In order to prevent future regressions, there are a few scenarios
|
||||
that need to be tested with each change to this area of the code.
|
||||
|
||||
- Cross cluster search
|
||||
- Cross-cluster search
|
||||
- Ensure changes work properly in a CCS environment
|
||||
- A solid CCS environment involves various indices on all nodes including the controlling node.
|
||||
- Alias support
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import { Field } from 'ui/index_patterns/_field';
|
||||
import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import { KbnUrlProvider } from 'ui/url';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
@ -101,8 +102,9 @@ uiRoutes
|
|||
}
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($scope, $route, $timeout, $http, Private, docTitle, config) {
|
||||
controller: function FieldEditorPageController($scope, $route, $timeout, $http, Private, config) {
|
||||
const getConfig = (...args) => config.get(...args);
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
const fieldFormatEditors = Private(RegistryFieldFormatEditorsProvider);
|
||||
const kbnUrl = Private(KbnUrlProvider);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
import './index_header';
|
||||
import './create_edit_field';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import { KbnUrlProvider } from 'ui/url';
|
||||
import { IndicesEditSectionsProvider } from './edit_sections';
|
||||
import { fatalError, toastNotifications } from 'ui/notify';
|
||||
|
@ -170,7 +171,7 @@ uiRoutes
|
|||
|
||||
uiModules.get('apps/management')
|
||||
.controller('managementIndexPatternsEdit', function (
|
||||
$scope, $location, $route, config, indexPatterns, Private, AppState, docTitle, confirmModal) {
|
||||
$scope, $location, $route, config, indexPatterns, Private, AppState, confirmModal) {
|
||||
const $state = $scope.state = new AppState();
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
const indexPatternListProvider = Private(IndexPatternListFactory)();
|
||||
|
@ -182,6 +183,7 @@ uiModules.get('apps/management')
|
|||
$scope.indexPatternListProvider = indexPatternListProvider;
|
||||
$scope.indexPattern.tags = indexPatternListProvider.getIndexPatternTags($scope.indexPattern);
|
||||
$scope.getFieldInfo = indexPatternListProvider.getFieldInfo;
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
docTitle.change($scope.indexPattern.title);
|
||||
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
|
|
|
@ -25,6 +25,7 @@ import 'ui/visualize';
|
|||
import 'ui/collapsible_sidebar';
|
||||
import 'ui/search_bar';
|
||||
import 'ui/apply_filters';
|
||||
import 'ui/listen';
|
||||
import chrome from 'ui/chrome';
|
||||
import React from 'react';
|
||||
import angular from 'angular';
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'ui/draggable/draggable_handle';
|
|||
import './saved_visualizations/_saved_vis';
|
||||
import './saved_visualizations/saved_visualizations';
|
||||
import 'ui/filters/sort_prefix_first';
|
||||
import 'ui/filter_bar';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import visualizeListingTemplate from './listing/visualize_listing.html';
|
||||
import { VisualizeListingController } from './listing/visualize_listing';
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import 'ui/pager_control';
|
||||
import 'ui/pager';
|
||||
import 'ui/directives/kbn_href';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
|
|
|
@ -162,7 +162,9 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -275,6 +277,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -310,6 +313,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
disabled=""
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -376,9 +380,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
}
|
||||
>
|
||||
<EuiModal
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
className="visNewVisDialog"
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
role="menu"
|
||||
>
|
||||
<EuiFocusTrap
|
||||
clickOutsideDisables={false}
|
||||
|
@ -439,7 +445,9 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -552,6 +560,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -587,6 +596,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
disabled=""
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -657,7 +667,9 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -770,6 +782,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -805,6 +818,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
disabled=""
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -869,8 +883,10 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
/>
|
||||
</SideEffect(FocusWatcher)>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
className="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
onKeyDown={[Function]}
|
||||
role="menu"
|
||||
tabIndex={0}
|
||||
>
|
||||
<EuiI18n
|
||||
|
@ -1165,6 +1181,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visWithSearch"
|
||||
|
@ -1177,6 +1194,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -1261,6 +1279,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-vis"
|
||||
|
@ -1273,6 +1292,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -1590,7 +1610,9 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -1701,6 +1723,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -1735,6 +1758,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -1801,9 +1825,11 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
}
|
||||
>
|
||||
<EuiModal
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
className="visNewVisDialog"
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
role="menu"
|
||||
>
|
||||
<EuiFocusTrap
|
||||
clickOutsideDisables={false}
|
||||
|
@ -1864,7 +1890,9 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -1975,6 +2003,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -2009,6 +2038,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -2079,7 +2109,9 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
data-focus-lock-disabled="false"
|
||||
>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
class="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
|
@ -2190,6 +2222,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-vis"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -2224,6 +2257,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
class="euiKeyPadMenuItem visNewVisDialog__type"
|
||||
data-test-subj="visType-visWithSearch"
|
||||
data-vis-stage="production"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -2288,8 +2322,10 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
/>
|
||||
</SideEffect(FocusWatcher)>
|
||||
<div
|
||||
aria-label="Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further."
|
||||
className="euiModal euiModal--maxWidth-default visNewVisDialog"
|
||||
onKeyDown={[Function]}
|
||||
role="menu"
|
||||
tabIndex={0}
|
||||
>
|
||||
<EuiI18n
|
||||
|
@ -2572,6 +2608,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-vis"
|
||||
|
@ -2584,6 +2621,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
@ -2668,6 +2706,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
>
|
||||
<button
|
||||
aria-describedby="visTypeDescription-visWithSearch"
|
||||
|
@ -2680,6 +2719,7 @@ exports[`NewVisModal should render as expected 1`] = `
|
|||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { EuiModal, EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { VisualizeConstants } from '../visualize_constants';
|
||||
|
||||
|
@ -64,13 +65,26 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
return null;
|
||||
}
|
||||
|
||||
const visNewVisDialogAriaLabel = i18n.translate(
|
||||
'kbn.visualize.newVisWizard.helpTextAriaLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Start creating your visualization by selecting a type for that visualization. Hit escape to close this modal. Hit Tab key to go further.',
|
||||
}
|
||||
);
|
||||
|
||||
const selectionModal =
|
||||
this.state.showSearchVisModal && this.state.visType ? (
|
||||
<EuiModal onClose={this.onCloseModal} className="visNewVisSearchDialog">
|
||||
<SearchSelection onSearchSelected={this.onSearchSelected} visType={this.state.visType} />
|
||||
</EuiModal>
|
||||
) : (
|
||||
<EuiModal onClose={this.onCloseModal} className="visNewVisDialog">
|
||||
<EuiModal
|
||||
onClose={this.onCloseModal}
|
||||
className="visNewVisDialog"
|
||||
aria-label={visNewVisDialogAriaLabel}
|
||||
role="menu"
|
||||
>
|
||||
<TypeSelection
|
||||
showExperimental={this.isLabsEnabled}
|
||||
onVisTypeSelected={this.onVisTypeSelected}
|
||||
|
|
|
@ -209,6 +209,7 @@ class TypeSelection extends React.Component<TypeSelectionProps, TypeSelectionSta
|
|||
data-vis-stage={visType.stage}
|
||||
disabled={isDisabled}
|
||||
aria-describedby={`visTypeDescription-${visType.name}`}
|
||||
role="menuitem"
|
||||
{...stage}
|
||||
>
|
||||
<VisTypeIcon visType={visType} />
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
|
||||
import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
|
||||
|
||||
export function corednsMetricsSpecProvider(server, context) {
|
||||
const moduleName = 'coredns';
|
||||
return {
|
||||
id: 'corednsMetrics',
|
||||
name: i18n.translate('kbn.server.tutorials.corednsMetrics.nameTitle', {
|
||||
defaultMessage: 'CoreDNS metrics',
|
||||
}),
|
||||
category: TUTORIAL_CATEGORY.METRICS,
|
||||
shortDescription: i18n.translate('kbn.server.tutorials.corednsMetrics.shortDescription', {
|
||||
defaultMessage: 'Fetch monitoring metrics from the CoreDNS server.',
|
||||
}),
|
||||
longDescription: i18n.translate('kbn.server.tutorials.corednsMetrics.longDescription', {
|
||||
defaultMessage: 'The `coredns` Metricbeat module fetches monitoring metrics from CoreDNS. \
|
||||
[Learn more]({learnMoreLink}).',
|
||||
values: {
|
||||
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-coredns.html',
|
||||
},
|
||||
}),
|
||||
// euiIconType: 'logoCoreDNS',
|
||||
artifacts: {
|
||||
application: {
|
||||
label: i18n.translate('kbn.server.tutorials.corednsMetrics.artifacts.application.label', {
|
||||
defaultMessage: 'Discover',
|
||||
}),
|
||||
path: '/app/kibana#/discover'
|
||||
},
|
||||
dashboards: [],
|
||||
exportedFields: {
|
||||
documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-coredns.html'
|
||||
}
|
||||
},
|
||||
completionTimeMinutes: 10,
|
||||
// previewImagePath: '/plugins/kibana/home/tutorial_resources/coredns_metrics/screenshot.png',
|
||||
onPrem: onPremInstructions(moduleName, null, null, null, context),
|
||||
elasticCloud: cloudInstructions(moduleName),
|
||||
onPremElasticCloud: onPremCloudInstructions(moduleName)
|
||||
};
|
||||
}
|
|
@ -68,6 +68,7 @@ import { mssqlMetricsSpecProvider } from './mssql_metrics';
|
|||
import { natsMetricsSpecProvider } from './nats_metrics';
|
||||
import { natsLogsSpecProvider } from './nats_logs';
|
||||
import { zeekLogsSpecProvider } from './zeek_logs';
|
||||
import { corednsMetricsSpecProvider } from './coredns_metrics';
|
||||
|
||||
export function registerTutorials(server) {
|
||||
server.registerTutorial(systemLogsSpecProvider);
|
||||
|
@ -121,4 +122,5 @@ export function registerTutorials(server) {
|
|||
server.registerTutorial(natsMetricsSpecProvider);
|
||||
server.registerTutorial(natsLogsSpecProvider);
|
||||
server.registerTutorial(zeekLogsSpecProvider);
|
||||
server.registerTutorial(corednsMetricsSpecProvider);
|
||||
}
|
||||
|
|
|
@ -302,19 +302,6 @@ export function getUiSettingDefaults() {
|
|||
}),
|
||||
category: ['discover'],
|
||||
},
|
||||
'courier:maxSegmentCount': {
|
||||
name: i18n.translate('kbn.advancedSettings.courier.maxSegmentCountTitle', {
|
||||
defaultMessage: 'Maximum segment count',
|
||||
}),
|
||||
value: 30,
|
||||
description: i18n.translate('kbn.advancedSettings.courier.maxSegmentCountText', {
|
||||
defaultMessage:
|
||||
'Requests in discover are split into segments to prevent massive requests from being sent to elasticsearch. ' +
|
||||
'This setting attempts to prevent the list of segments from getting too long, ' +
|
||||
'which might cause requests to take much longer to process.',
|
||||
}),
|
||||
category: ['search'],
|
||||
},
|
||||
'courier:ignoreFilterIfFieldNotInIndex': {
|
||||
name: i18n.translate('kbn.advancedSettings.courier.ignoreFilterTitle', {
|
||||
defaultMessage: 'Ignore filter(s)',
|
||||
|
|
|
@ -27,6 +27,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/markdown_vis/markdown_vis'
|
||||
],
|
||||
interpreter: ['plugins/markdown_vis/markdown_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const kibanaMarkdown = () => ({
|
||||
|
@ -25,7 +26,7 @@ export const kibanaMarkdown = () => ({
|
|||
context: {
|
||||
types: [],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.markdown.help', {
|
||||
help: i18n.translate('markdownVis.function.help', {
|
||||
defaultMessage: 'Markdown visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -48,3 +49,5 @@ export const kibanaMarkdown = () => ({
|
|||
};
|
||||
}
|
||||
});
|
||||
|
||||
functionsRegistry.register(kibanaMarkdown);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { kibanaMarkdown } from './markdown';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { kibanaMarkdown } from './markdown_fn';
|
||||
|
||||
describe('interpreter/functions#markdown', () => {
|
||||
const fn = functionWrapper(kibanaMarkdown);
|
|
@ -27,6 +27,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/metric_vis/metric_vis'
|
||||
],
|
||||
interpreter: ['plugins/metric_vis/metric_vis_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const metric = () => ({
|
||||
|
@ -27,7 +28,7 @@ export const metric = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.metric.help', {
|
||||
help: i18n.translate('metricVis.function.help', {
|
||||
defaultMessage: 'Metric visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -53,3 +54,5 @@ export const metric = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(metric);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { metric } from './metric';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { metric } from './metric_vis_fn';
|
||||
|
||||
describe('interpreter/functions#metric', () => {
|
||||
const fn = functionWrapper(metric);
|
|
@ -78,7 +78,7 @@
|
|||
type="number"
|
||||
class="form-control"
|
||||
name="range.from"
|
||||
greater-or-equal-than="{{getGreaterThan($index)}}"
|
||||
greater-or-equal-than="getGreaterThan($index)"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
|
@ -89,7 +89,7 @@
|
|||
type="number"
|
||||
class="form-control"
|
||||
name="range.to"
|
||||
greater-or-equal-than="{{range.from}}"
|
||||
greater-or-equal-than="range.from"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import 'ui/directives/inequality';
|
||||
import metricVisParamsTemplate from './metric_vis_params.html';
|
||||
import _ from 'lodash';
|
||||
const module = uiModules.get('kibana');
|
||||
|
@ -54,7 +55,7 @@ module.directive('metricVisParams', function (i18n) {
|
|||
};
|
||||
|
||||
$scope.getGreaterThan = function (index) {
|
||||
if (index === 0) return 0;
|
||||
if (index === 0) return -Infinity;
|
||||
return $scope.editorState.params.metric.colorsRange[index - 1].to;
|
||||
};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/metrics/kbn_vis_types',
|
||||
],
|
||||
interpreter: ['plugins/metrics/tsvb_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
},
|
||||
|
||||
|
|
|
@ -1,173 +1,169 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
import { share } from 'rxjs/operators';
|
||||
import VisEditorVisualization from './vis_editor_visualization';
|
||||
import Visualization from './visualization';
|
||||
import VisPicker from './vis_picker';
|
||||
import PanelConfig from './panel_config';
|
||||
import brushHandler from '../lib/create_brush_handler';
|
||||
import { fetchIndexPatternFields } from '../lib/fetch_fields';
|
||||
|
||||
class VisEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { vis } = props;
|
||||
this.appState = vis.API.getAppState();
|
||||
this.state = {
|
||||
model: props.visParams,
|
||||
dirty: false,
|
||||
autoApply: true,
|
||||
visFields: props.visFields
|
||||
};
|
||||
this.onBrush = brushHandler(props.vis.API.timeFilter);
|
||||
this.visDataSubject = new Rx.Subject();
|
||||
this.visData$ = this.visDataSubject.asObservable().pipe(share());
|
||||
}
|
||||
|
||||
get uiState() {
|
||||
return this.props.vis.getUiState();
|
||||
}
|
||||
|
||||
getConfig = (...args) => {
|
||||
return this.props.config.get(...args);
|
||||
};
|
||||
|
||||
handleUiState = (field, value) => {
|
||||
this.props.vis.uiStateVal(field, value);
|
||||
};
|
||||
|
||||
handleChange = async (partialModel) => {
|
||||
const nextModel = { ...this.state.model, ...partialModel };
|
||||
this.props.vis.params = nextModel;
|
||||
if (this.state.autoApply) {
|
||||
this.props.vis.updateState();
|
||||
}
|
||||
this.setState({
|
||||
model: nextModel,
|
||||
dirty: !this.state.autoApply
|
||||
});
|
||||
const { params, fields } = this.props.vis;
|
||||
fetchIndexPatternFields(params, fields).then(visFields => {
|
||||
this.setState({ visFields });
|
||||
});
|
||||
};
|
||||
|
||||
handleCommit = () => {
|
||||
this.props.vis.updateState();
|
||||
this.setState({ dirty: false });
|
||||
};
|
||||
|
||||
handleAutoApplyToggle = (event) => {
|
||||
this.setState({ autoApply: event.target.checked });
|
||||
};
|
||||
|
||||
onDataChange = ({ visData }) => {
|
||||
this.visDataSubject.next(visData);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.isEditorMode) {
|
||||
if (!this.props.visParams || !this.props.visData) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Visualization
|
||||
dateFormat={this.props.config.get('dateFormat')}
|
||||
onBrush={this.onBrush}
|
||||
onUiState={this.handleUiState}
|
||||
uiState={this.uiState}
|
||||
model={this.props.visParams}
|
||||
visData={this.props.visData}
|
||||
getConfig={this.getConfig}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { model } = this.state;
|
||||
|
||||
if (model) {
|
||||
return (
|
||||
<div className="tvbEditor">
|
||||
<div className="tvbEditor--hideForReporting">
|
||||
<VisPicker model={model} onChange={this.handleChange} />
|
||||
</div>
|
||||
<VisEditorVisualization
|
||||
dirty={this.state.dirty}
|
||||
autoApply={this.state.autoApply}
|
||||
model={model}
|
||||
appState={this.appState}
|
||||
savedObj={this.props.savedObj}
|
||||
timeRange={this.props.timeRange}
|
||||
onUiState={this.handleUiState}
|
||||
uiState={this.uiState}
|
||||
onBrush={this.onBrush}
|
||||
onCommit={this.handleCommit}
|
||||
onToggleAutoApply={this.handleAutoApplyToggle}
|
||||
onChange={this.handleChange}
|
||||
title={this.props.vis.title}
|
||||
description={this.props.vis.description}
|
||||
dateFormat={this.props.config.get('dateFormat')}
|
||||
onDataChange={this.onDataChange}
|
||||
/>
|
||||
<div className="tvbEditor--hideForReporting">
|
||||
<PanelConfig
|
||||
fields={this.state.visFields}
|
||||
model={model}
|
||||
visData$={this.visData$}
|
||||
dateFormat={this.props.config.get('dateFormat')}
|
||||
onChange={this.handleChange}
|
||||
getConfig={this.getConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.renderComplete();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.props.renderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
VisEditor.defaultProps = {
|
||||
visData: {}
|
||||
};
|
||||
|
||||
VisEditor.propTypes = {
|
||||
vis: PropTypes.object,
|
||||
visData: PropTypes.object,
|
||||
visFields: PropTypes.object,
|
||||
renderComplete: PropTypes.func,
|
||||
config: PropTypes.object,
|
||||
isEditorMode: PropTypes.bool,
|
||||
savedObj: PropTypes.object,
|
||||
timeRange: PropTypes.object,
|
||||
};
|
||||
|
||||
export default VisEditor;
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
import { share } from 'rxjs/operators';
|
||||
import VisEditorVisualization from './vis_editor_visualization';
|
||||
import Visualization from './visualization';
|
||||
import VisPicker from './vis_picker';
|
||||
import PanelConfig from './panel_config';
|
||||
import brushHandler from '../lib/create_brush_handler';
|
||||
import { fetchIndexPatternFields } from '../lib/fetch_fields';
|
||||
|
||||
class VisEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { vis } = props;
|
||||
this.appState = vis.API.getAppState();
|
||||
this.state = {
|
||||
model: props.visParams,
|
||||
dirty: false,
|
||||
autoApply: true,
|
||||
visFields: props.visFields
|
||||
};
|
||||
this.onBrush = brushHandler(props.vis.API.timeFilter);
|
||||
this.visDataSubject = new Rx.Subject();
|
||||
this.visData$ = this.visDataSubject.asObservable().pipe(share());
|
||||
}
|
||||
|
||||
get uiState() {
|
||||
return this.props.vis.getUiState();
|
||||
}
|
||||
|
||||
getConfig = (...args) => {
|
||||
return this.props.config.get(...args);
|
||||
};
|
||||
|
||||
handleUiState = (field, value) => {
|
||||
this.props.vis.uiStateVal(field, value);
|
||||
};
|
||||
|
||||
handleChange = async (partialModel) => {
|
||||
const nextModel = { ...this.state.model, ...partialModel };
|
||||
this.props.vis.params = nextModel;
|
||||
if (this.state.autoApply) {
|
||||
this.props.vis.updateState();
|
||||
}
|
||||
this.setState({
|
||||
model: nextModel,
|
||||
dirty: !this.state.autoApply
|
||||
});
|
||||
const { params, fields } = this.props.vis;
|
||||
fetchIndexPatternFields(params, fields).then(visFields => {
|
||||
this.setState({ visFields });
|
||||
});
|
||||
};
|
||||
|
||||
handleCommit = () => {
|
||||
this.props.vis.updateState();
|
||||
this.setState({ dirty: false });
|
||||
};
|
||||
|
||||
handleAutoApplyToggle = (event) => {
|
||||
this.setState({ autoApply: event.target.checked });
|
||||
};
|
||||
|
||||
onDataChange = ({ visData }) => {
|
||||
this.visDataSubject.next(visData);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.isEditorMode) {
|
||||
if (!this.props.visParams || !this.props.visData) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Visualization
|
||||
dateFormat={this.props.config.get('dateFormat')}
|
||||
onBrush={this.onBrush}
|
||||
onUiState={this.handleUiState}
|
||||
uiState={this.uiState}
|
||||
model={this.props.visParams}
|
||||
visData={this.props.visData}
|
||||
getConfig={this.getConfig}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { model } = this.state;
|
||||
|
||||
if (model) {
|
||||
return (
|
||||
<div className="tvbEditor">
|
||||
<div className="tvbEditor--hideForReporting">
|
||||
<VisPicker model={model} onChange={this.handleChange} />
|
||||
</div>
|
||||
<VisEditorVisualization
|
||||
dirty={this.state.dirty}
|
||||
autoApply={this.state.autoApply}
|
||||
model={model}
|
||||
appState={this.appState}
|
||||
savedObj={this.props.savedObj}
|
||||
timeRange={this.props.timeRange}
|
||||
uiState={this.uiState}
|
||||
onCommit={this.handleCommit}
|
||||
onToggleAutoApply={this.handleAutoApplyToggle}
|
||||
title={this.props.vis.title}
|
||||
description={this.props.vis.description}
|
||||
onDataChange={this.onDataChange}
|
||||
/>
|
||||
<div className="tvbEditor--hideForReporting">
|
||||
<PanelConfig
|
||||
fields={this.state.visFields}
|
||||
model={model}
|
||||
visData$={this.visData$}
|
||||
dateFormat={this.props.config.get('dateFormat')}
|
||||
onChange={this.handleChange}
|
||||
getConfig={this.getConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.renderComplete();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.props.renderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
VisEditor.defaultProps = {
|
||||
visData: {}
|
||||
};
|
||||
|
||||
VisEditor.propTypes = {
|
||||
vis: PropTypes.object,
|
||||
visData: PropTypes.object,
|
||||
visFields: PropTypes.object,
|
||||
renderComplete: PropTypes.func,
|
||||
config: PropTypes.object,
|
||||
isEditorMode: PropTypes.bool,
|
||||
savedObj: PropTypes.object,
|
||||
timeRange: PropTypes.object,
|
||||
};
|
||||
|
||||
export default VisEditor;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { get, isEqual } from 'lodash';
|
||||
import { keyCodes, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui';
|
||||
import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
|
@ -35,74 +35,54 @@ class VisEditorVisualization extends Component {
|
|||
panelInterval: 0,
|
||||
};
|
||||
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.onSizeHandleKeyDown = this.onSizeHandleKeyDown.bind(this);
|
||||
|
||||
this._visEl = React.createRef();
|
||||
this._subscription = null;
|
||||
}
|
||||
|
||||
handleMouseDown() {
|
||||
this.setState({ dragging: true });
|
||||
}
|
||||
|
||||
handleMouseUp() {
|
||||
this.setState({ dragging: false });
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.handleMouseMove = (event) => {
|
||||
if (this.state.dragging) {
|
||||
this.setState((prevState) => ({
|
||||
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY),
|
||||
}));
|
||||
}
|
||||
};
|
||||
window.addEventListener('mousemove', this.handleMouseMove);
|
||||
handleMouseDown = () => {
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
if (this._handler) {
|
||||
this._handler.destroy();
|
||||
}
|
||||
if (this._subscription) {
|
||||
this._subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate = () => {
|
||||
this._handler.update({
|
||||
timeRange: this.props.timeRange,
|
||||
});
|
||||
this.setState({ dragging: true });
|
||||
};
|
||||
|
||||
_loadVisualization() {
|
||||
getVisualizeLoader().then(loader => {
|
||||
if (!this._visEl.current) {
|
||||
// In case the visualize loader isn't done before the component is unmounted.
|
||||
return;
|
||||
}
|
||||
handleMouseUp = () => {
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
this.setState({ dragging: false });
|
||||
};
|
||||
|
||||
this._loader = loader;
|
||||
this._handler = this._loader.embedVisualizationWithSavedObject(this._visEl.current, this.props.savedObj, {
|
||||
uiState: this.props.uiState,
|
||||
listenOnChange: false,
|
||||
timeRange: this.props.timeRange,
|
||||
appState: this.props.appState,
|
||||
});
|
||||
handleMouseMove = (event) => {
|
||||
if (this.state.dragging) {
|
||||
this.setState((prevState) => ({
|
||||
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
this._subscription = this._handler.data$.subscribe((data) => {
|
||||
this.setPanelInterval(data.visData);
|
||||
this.props.onDataChange(data);
|
||||
});
|
||||
async _loadVisualization() {
|
||||
const loader = await getVisualizeLoader();
|
||||
|
||||
if (this._handlerUpdateHasAlreadyBeenTriggered) {
|
||||
this.onUpdate();
|
||||
}
|
||||
if (!this._visEl.current) {
|
||||
// In case the visualize loader isn't done before the component is unmounted.
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
uiState,
|
||||
timeRange,
|
||||
appState,
|
||||
savedObj,
|
||||
onDataChange
|
||||
} = this.props;
|
||||
|
||||
this._handler = loader.embedVisualizationWithSavedObject(this._visEl.current, savedObj, {
|
||||
listenOnChange: false,
|
||||
uiState,
|
||||
timeRange,
|
||||
appState,
|
||||
});
|
||||
|
||||
this._subscription = this._handler.data$.subscribe((data) => {
|
||||
this.setPanelInterval(data.visData);
|
||||
onDataChange(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -114,26 +94,13 @@ class VisEditorVisualization extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!this._handler) {
|
||||
this._handlerUpdateHasAlreadyBeenTriggered = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.onUpdate();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._loadVisualization();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the chart height when pressing up/down while the drag handle
|
||||
* for resizing has the focus.
|
||||
* We use 15px steps to do the scaling and make sure the chart has at least its
|
||||
* defined minimum width (MIN_CHART_HEIGHT).
|
||||
*/
|
||||
onSizeHandleKeyDown(ev) {
|
||||
onSizeHandleKeyDown = (ev) => {
|
||||
const { keyCode } = ev;
|
||||
if (keyCode === keyCodes.UP || keyCode === keyCodes.DOWN) {
|
||||
ev.preventDefault();
|
||||
|
@ -174,9 +141,41 @@ class VisEditorVisualization extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
if (this._handler) {
|
||||
this._handler.destroy();
|
||||
}
|
||||
if (this._subscription) {
|
||||
this._subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('mousemove', this.handleMouseMove);
|
||||
this._loadVisualization();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this._handler && !isEqual(this.props.timeRange, prevProps.timeRange)) {
|
||||
this._handler.update({
|
||||
timeRange: this.props.timeRange,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dirty, autoApply } = this.props;
|
||||
const {
|
||||
dirty,
|
||||
autoApply,
|
||||
title,
|
||||
description,
|
||||
onToggleAutoApply,
|
||||
onCommit
|
||||
} = this.props;
|
||||
const style = { height: this.state.height };
|
||||
|
||||
if (this.state.dragging) {
|
||||
style.userSelect = 'none';
|
||||
}
|
||||
|
@ -209,7 +208,7 @@ class VisEditorVisualization extends Component {
|
|||
defaultMessage="Auto apply"
|
||||
/>)}
|
||||
checked={autoApply}
|
||||
onChange={this.props.onToggleAutoApply}
|
||||
onChange={onToggleAutoApply}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
@ -220,9 +219,7 @@ class VisEditorVisualization extends Component {
|
|||
<FormattedMessage
|
||||
id="tsvb.visEditorVisualization.panelInterval"
|
||||
defaultMessage="Interval: {panelInterval}"
|
||||
values={{
|
||||
panelInterval: panelInterval,
|
||||
}}
|
||||
values={{ panelInterval }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
@ -239,7 +236,7 @@ class VisEditorVisualization extends Component {
|
|||
|
||||
{!autoApply &&
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="play" fill size="s" onClick={this.props.onCommit} disabled={!dirty}>
|
||||
<EuiButton iconType="play" fill size="s" onClick={onCommit} disabled={!dirty}>
|
||||
<FormattedMessage
|
||||
id="tsvb.visEditorVisualization.applyChangesLabel"
|
||||
defaultMessage="Apply changes"
|
||||
|
@ -257,8 +254,8 @@ class VisEditorVisualization extends Component {
|
|||
className="tvbEditorVisualization"
|
||||
data-shared-items-container
|
||||
data-shared-item
|
||||
data-title={this.props.title}
|
||||
data-description={this.props.description}
|
||||
data-title={title}
|
||||
data-description={description}
|
||||
data-render-complete="disabled"
|
||||
ref={this._visEl}
|
||||
/>
|
||||
|
@ -284,17 +281,13 @@ class VisEditorVisualization extends Component {
|
|||
|
||||
VisEditorVisualization.propTypes = {
|
||||
model: PropTypes.object,
|
||||
onBrush: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onCommit: PropTypes.func,
|
||||
onUiState: PropTypes.func,
|
||||
uiState: PropTypes.object,
|
||||
onToggleAutoApply: PropTypes.func,
|
||||
savedObj: PropTypes.object,
|
||||
timeRange: PropTypes.object,
|
||||
dirty: PropTypes.bool,
|
||||
autoApply: PropTypes.bool,
|
||||
dateFormat: PropTypes.string,
|
||||
appState: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -35,13 +35,9 @@ describe('getVisualizeLoader', () => {
|
|||
}
|
||||
};
|
||||
const loaderMock = {
|
||||
embedVisualizationWithSavedObject: () => {
|
||||
return handlerMock;
|
||||
}
|
||||
};
|
||||
require('ui/visualize/loader/visualize_loader').getVisualizeLoader = async () => {
|
||||
return loaderMock;
|
||||
embedVisualizationWithSavedObject: () => handlerMock,
|
||||
};
|
||||
require('ui/visualize/loader/visualize_loader').getVisualizeLoader = async () => loaderMock;
|
||||
});
|
||||
|
||||
it('should not call _handler.update until getVisualizeLoader returns _handler', async () => {
|
||||
|
@ -50,12 +46,25 @@ describe('getVisualizeLoader', () => {
|
|||
);
|
||||
|
||||
// Set prop to force DOM change and componentDidUpdate to be triggered
|
||||
wrapper.setProps({ dirty: true });
|
||||
wrapper.setProps({
|
||||
timeRange: {
|
||||
from: '2019-03-20T20:35:37.637Z',
|
||||
to: '2019-03-23T18:40:16.486Z'
|
||||
}
|
||||
});
|
||||
|
||||
expect(updateStub).not.toHaveBeenCalled();
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
wrapper.update();
|
||||
|
||||
// Set prop to force DOM change and componentDidUpdate to be triggered
|
||||
wrapper.setProps({
|
||||
timeRange: {
|
||||
from: 'now/d',
|
||||
to: 'now/d'
|
||||
}
|
||||
});
|
||||
|
||||
expect(updateStub).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MetricsRequestHandlerProvider } from 'plugins/metrics/kbn_vis_types/request_handler';
|
||||
import { MetricsRequestHandlerProvider } from './kbn_vis_types/request_handler';
|
||||
import { PersistedState } from 'ui/persisted_state';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
|
@ -34,7 +35,7 @@ export const tsvb = () => ({
|
|||
'null',
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.tsvb.help', {
|
||||
help: i18n.translate('tsvb.function.help', {
|
||||
defaultMessage: 'TSVB visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -78,3 +79,5 @@ export const tsvb = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(tsvb);
|
|
@ -24,6 +24,7 @@ export default function (kibana) {
|
|||
return new kibana.Plugin({
|
||||
uiExports: {
|
||||
visTypes: ['plugins/region_map/region_map_vis'],
|
||||
interpreter: ['plugins/region_map/region_map_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const regionmap = () => ({
|
||||
|
@ -27,7 +28,7 @@ export const regionmap = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.regionmap.help', {
|
||||
help: i18n.translate('regionMap.function.help', {
|
||||
defaultMessage: 'Regionmap visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -53,3 +54,5 @@ export const regionmap = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(regionmap);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { regionmap } from './regionmap';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { regionmap } from './region_map_fn';
|
||||
|
||||
describe('interpreter/functions#regionmap', () => {
|
||||
const fn = functionWrapper(regionmap);
|
|
@ -26,6 +26,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/table_vis/table_vis'
|
||||
],
|
||||
interpreter: ['plugins/table_vis/table_vis_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { LegacyResponseHandlerProvider as legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -28,7 +29,7 @@ export const kibanaTable = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.table.help', {
|
||||
help: i18n.translate('tableVis.function.help', {
|
||||
defaultMessage: 'Table visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -57,3 +58,5 @@ export const kibanaTable = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(kibanaTable);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { kibanaTable } from './table';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { kibanaTable } from './table_vis_fn';
|
||||
|
||||
const mockResponseHandler = jest.fn().mockReturnValue(Promise.resolve({
|
||||
tables: [{ columns: [], rows: [] }],
|
|
@ -24,6 +24,7 @@ export default function (kibana) {
|
|||
return new kibana.Plugin({
|
||||
uiExports: {
|
||||
visTypes: ['plugins/tagcloud/tag_cloud_vis'],
|
||||
interpreter: ['plugins/tagcloud/tag_cloud_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const tagcloud = () => ({
|
||||
|
@ -27,7 +28,7 @@ export const tagcloud = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.tagcloud.help', {
|
||||
help: i18n.translate('tagCloud.function.help', {
|
||||
defaultMessage: 'Tagcloud visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -53,3 +54,5 @@ export const tagcloud = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(tagcloud);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { tagcloud } from './tagcloud';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { tagcloud } from './tag_cloud_fn';
|
||||
|
||||
describe('interpreter/functions#tagcloud', () => {
|
||||
const fn = functionWrapper(tagcloud);
|
|
@ -25,6 +25,7 @@ export default function (kibana) {
|
|||
return new kibana.Plugin({
|
||||
uiExports: {
|
||||
visTypes: ['plugins/tile_map/tile_map_vis'],
|
||||
interpreter: ['plugins/tile_map/tilemap_fn'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
},
|
||||
init(server) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -28,7 +29,7 @@ export const tilemap = () => ({
|
|||
'kibana_datatable'
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.tilemap.help', {
|
||||
help: i18n.translate('tileMap.function.help', {
|
||||
defaultMessage: 'Tilemap visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -61,3 +62,5 @@ export const tilemap = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(tilemap);
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../test_helpers';
|
||||
import { tilemap } from './tilemap';
|
||||
import { functionWrapper } from '../../interpreter/test_helpers';
|
||||
import { tilemap } from './tilemap_fn';
|
||||
|
||||
jest.mock('ui/vis/map/convert_to_geojson', () => ({
|
||||
convertToGeoJson: jest.fn().mockReturnValue({
|
|
@ -59,6 +59,7 @@ export default function (kibana) {
|
|||
visTypes: [
|
||||
'plugins/timelion/vis'
|
||||
],
|
||||
interpreter: ['plugins/timelion/timelion_vis_fn'],
|
||||
home: [
|
||||
'plugins/timelion/register_feature'
|
||||
],
|
||||
|
|
|
@ -32,6 +32,14 @@ import 'uiExports/fieldFormats';
|
|||
import 'uiExports/savedObjectTypes';
|
||||
|
||||
require('ui/autoload/all');
|
||||
|
||||
// TODO: remove ui imports completely (move to plugins)
|
||||
import 'ui/directives/input_focus';
|
||||
import 'ui/directives/saved_object_finder';
|
||||
import 'ui/listen';
|
||||
import 'ui/kbn_top_nav';
|
||||
import 'ui/saved_objects/ui/saved_object_save_as_checkbox';
|
||||
|
||||
require('plugins/timelion/directives/cells/cells');
|
||||
require('plugins/timelion/directives/fixed_element');
|
||||
require('plugins/timelion/directives/fullscreen/fullscreen');
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimelionRequestHandlerProvider } from 'plugins/timelion/vis/timelion_request_handler';
|
||||
import { TimelionRequestHandlerProvider } from './vis/timelion_request_handler';
|
||||
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
|
@ -33,7 +34,7 @@ export const timelionVis = () => ({
|
|||
'null',
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.timelion.help', {
|
||||
help: i18n.translate('timelion.function.help', {
|
||||
defaultMessage: 'Timelion visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -75,3 +76,5 @@ export const timelionVis = () => ({
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(timelionVis);
|
|
@ -25,6 +25,7 @@ export default kibana => new kibana.Plugin({
|
|||
|
||||
uiExports: {
|
||||
visTypes: ['plugins/vega/vega_type'],
|
||||
interpreter: ['plugins/vega/vega_fn'],
|
||||
injectDefaultVars: server => ({ vegaConfig: server.config().get('vega') }),
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
},
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import chrome from 'ui/chrome';
|
||||
import { VegaRequestHandlerProvider } from 'plugins/vega/vega_request_handler';
|
||||
import { VegaRequestHandlerProvider } from './vega_request_handler';
|
||||
|
||||
export const vega = () => ({
|
||||
name: 'vega',
|
||||
|
@ -31,7 +32,7 @@ export const vega = () => ({
|
|||
'null',
|
||||
],
|
||||
},
|
||||
help: i18n.translate('interpreter.functions.vega.help', {
|
||||
help: i18n.translate('vega.function.help', {
|
||||
defaultMessage: 'Vega visualization'
|
||||
}),
|
||||
args: {
|
||||
|
@ -66,3 +67,5 @@ export const vega = () => ({
|
|||
};
|
||||
}
|
||||
});
|
||||
|
||||
functionsRegistry.register(vega);
|
2
src/legacy/server/kbn_server.d.ts
vendored
2
src/legacy/server/kbn_server.d.ts
vendored
|
@ -62,7 +62,7 @@ type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promi
|
|||
type Unpromise<T> = T extends Promise<infer U> ? U : T;
|
||||
export default class KbnServer {
|
||||
public readonly newPlatform: {
|
||||
start: {
|
||||
setup: {
|
||||
core: {
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
};
|
||||
|
|
|
@ -35,5 +35,8 @@ export function includedFields(type, fields) {
|
|||
.map(f => `${sourceType}.${f}`)
|
||||
.concat('namespace')
|
||||
.concat('type')
|
||||
.concat('references')
|
||||
.concat('migrationVersion')
|
||||
.concat('updated_at')
|
||||
.concat(fields); // v5 compatibility
|
||||
}
|
||||
|
|
|
@ -26,33 +26,51 @@ describe('includedFields', () => {
|
|||
|
||||
it('includes type', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(4);
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('type');
|
||||
});
|
||||
|
||||
it('includes namespace', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(4);
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('namespace');
|
||||
});
|
||||
|
||||
it('includes references', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('references');
|
||||
});
|
||||
|
||||
it('includes migrationVersion', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('migrationVersion');
|
||||
});
|
||||
|
||||
it('includes updated_at', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('updated_at');
|
||||
});
|
||||
|
||||
it('accepts field as string', () => {
|
||||
const fields = includedFields('config', 'foo');
|
||||
expect(fields).toHaveLength(4);
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('config.foo');
|
||||
});
|
||||
|
||||
it('accepts fields as an array', () => {
|
||||
const fields = includedFields('config', ['foo', 'bar']);
|
||||
|
||||
expect(fields).toHaveLength(6);
|
||||
expect(fields).toHaveLength(9);
|
||||
expect(fields).toContain('config.foo');
|
||||
expect(fields).toContain('config.bar');
|
||||
});
|
||||
|
||||
it('uses wildcard when type is not provided', () => {
|
||||
const fields = includedFields(undefined, 'foo');
|
||||
expect(fields).toHaveLength(4);
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toContain('*.foo');
|
||||
});
|
||||
|
||||
|
@ -60,7 +78,7 @@ describe('includedFields', () => {
|
|||
it('includes legacy field path', () => {
|
||||
const fields = includedFields('config', ['foo', 'bar']);
|
||||
|
||||
expect(fields).toHaveLength(6);
|
||||
expect(fields).toHaveLength(9);
|
||||
expect(fields).toContain('foo');
|
||||
expect(fields).toContain('bar');
|
||||
});
|
||||
|
|
|
@ -1224,7 +1224,15 @@ describe('SavedObjectsRepository', () => {
|
|||
expect(callAdminCluster).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
_source: ['foo.title', 'namespace', 'type', 'title'],
|
||||
_source: [
|
||||
'foo.title',
|
||||
'namespace',
|
||||
'type',
|
||||
'references',
|
||||
'migrationVersion',
|
||||
'updated_at',
|
||||
'title',
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -17,25 +17,236 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
import ngMock from 'ng_mock';
|
||||
import { EventsProvider } from '../events';
|
||||
import expect from '@kbn/expect';
|
||||
import '../private';
|
||||
import { createLegacyClass } from '../utils/legacy_class';
|
||||
|
||||
describe('events', function () {
|
||||
describe('Events', function () {
|
||||
require('test_utils/no_digest_promises').activateForSuite();
|
||||
|
||||
let events;
|
||||
let Events;
|
||||
let Promise;
|
||||
let eventsInstance;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
const Events = Private(EventsProvider);
|
||||
events = new Events();
|
||||
beforeEach(ngMock.inject(function ($injector, Private) {
|
||||
Promise = $injector.get('Promise');
|
||||
Events = Private(EventsProvider);
|
||||
eventsInstance = new Events();
|
||||
}));
|
||||
|
||||
it('should handle on events', function () {
|
||||
const obj = new Events();
|
||||
const prom = obj.on('test', function (message) {
|
||||
expect(message).to.equal('Hello World');
|
||||
});
|
||||
|
||||
obj.emit('test', 'Hello World');
|
||||
|
||||
return prom;
|
||||
});
|
||||
|
||||
it('should work with inherited objects', function () {
|
||||
createLegacyClass(MyEventedObject).inherits(Events);
|
||||
function MyEventedObject() {
|
||||
MyEventedObject.Super.call(this);
|
||||
}
|
||||
const obj = new MyEventedObject();
|
||||
|
||||
const prom = obj.on('test', function (message) {
|
||||
expect(message).to.equal('Hello World');
|
||||
});
|
||||
|
||||
obj.emit('test', 'Hello World');
|
||||
|
||||
return prom;
|
||||
});
|
||||
|
||||
it('should clear events when off is called', function () {
|
||||
const obj = new Events();
|
||||
obj.on('test', _.noop);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
expect(obj._listeners.test).to.have.length(1);
|
||||
obj.off();
|
||||
expect(obj._listeners).to.not.have.property('test');
|
||||
});
|
||||
|
||||
it('should clear a specific handler when off is called for an event', function () {
|
||||
const obj = new Events();
|
||||
const handler1 = sinon.stub();
|
||||
const handler2 = sinon.stub();
|
||||
obj.on('test', handler1);
|
||||
obj.on('test', handler2);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
obj.off('test', handler1);
|
||||
|
||||
return obj.emit('test', 'Hello World')
|
||||
.then(function () {
|
||||
sinon.assert.calledOnce(handler2);
|
||||
sinon.assert.notCalled(handler1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear a all handlers when off is called for an event', function () {
|
||||
const obj = new Events();
|
||||
const handler1 = sinon.stub();
|
||||
obj.on('test', handler1);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
obj.off('test');
|
||||
expect(obj._listeners).to.not.have.property('test');
|
||||
|
||||
return obj.emit('test', 'Hello World')
|
||||
.then(function () {
|
||||
sinon.assert.notCalled(handler1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple identical emits in the same tick', function () {
|
||||
const obj = new Events();
|
||||
const handler1 = sinon.stub();
|
||||
|
||||
obj.on('test', handler1);
|
||||
const emits = [
|
||||
obj.emit('test', 'one'),
|
||||
obj.emit('test', 'two'),
|
||||
obj.emit('test', 'three')
|
||||
];
|
||||
|
||||
return Promise
|
||||
.all(emits)
|
||||
.then(function () {
|
||||
expect(handler1.callCount).to.be(emits.length);
|
||||
expect(handler1.getCall(0).calledWith('one')).to.be(true);
|
||||
expect(handler1.getCall(1).calledWith('two')).to.be(true);
|
||||
expect(handler1.getCall(2).calledWith('three')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle emits from the handler', function () {
|
||||
const obj = new Events();
|
||||
const secondEmit = Promise.defer();
|
||||
const handler1 = sinon.spy(function () {
|
||||
if (handler1.calledTwice) {
|
||||
return;
|
||||
}
|
||||
obj.emit('test').then(_.bindKey(secondEmit, 'resolve'));
|
||||
});
|
||||
|
||||
obj.on('test', handler1);
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
obj.emit('test'),
|
||||
secondEmit.promise
|
||||
])
|
||||
.then(function () {
|
||||
expect(handler1.callCount).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only emit to handlers registered before emit is called', function () {
|
||||
const obj = new Events();
|
||||
const handler1 = sinon.stub();
|
||||
const handler2 = sinon.stub();
|
||||
|
||||
obj.on('test', handler1);
|
||||
const emits = [
|
||||
obj.emit('test', 'one'),
|
||||
obj.emit('test', 'two'),
|
||||
obj.emit('test', 'three')
|
||||
];
|
||||
|
||||
|
||||
return Promise.all(emits).then(function () {
|
||||
expect(handler1.callCount).to.be(emits.length);
|
||||
|
||||
obj.on('test', handler2);
|
||||
|
||||
const emits2 = [
|
||||
obj.emit('test', 'four'),
|
||||
obj.emit('test', 'five'),
|
||||
obj.emit('test', 'six')
|
||||
];
|
||||
|
||||
return Promise.all(emits2)
|
||||
.then(function () {
|
||||
expect(handler1.callCount).to.be(emits.length + emits2.length);
|
||||
expect(handler2.callCount).to.be(emits2.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass multiple arguments from the emitter', function () {
|
||||
const obj = new Events();
|
||||
const handler = sinon.stub();
|
||||
const payload = [
|
||||
'one',
|
||||
{ hello: 'tests' },
|
||||
null
|
||||
];
|
||||
|
||||
obj.on('test', handler);
|
||||
|
||||
return obj.emit('test', payload[0], payload[1], payload[2])
|
||||
.then(function () {
|
||||
expect(handler.callCount).to.be(1);
|
||||
expect(handler.calledWithExactly(payload[0], payload[1], payload[2])).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve the scope of the handler', function () {
|
||||
const obj = new Events();
|
||||
const expected = 'some value';
|
||||
let testValue;
|
||||
|
||||
function handler() {
|
||||
testValue = this.getVal();
|
||||
}
|
||||
handler.getVal = _.constant(expected);
|
||||
|
||||
obj.on('test', handler);
|
||||
return obj.emit('test')
|
||||
.then(function () {
|
||||
expect(testValue).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should always emit in the same order', function () {
|
||||
const handler = sinon.stub();
|
||||
|
||||
const obj = new Events();
|
||||
obj.on('block', _.partial(handler, 'block'));
|
||||
obj.on('last', _.partial(handler, 'last'));
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('block'),
|
||||
obj.emit('last')
|
||||
])
|
||||
.then(function () {
|
||||
expect(handler.callCount).to.be(10);
|
||||
handler.args.forEach(function (args, i) {
|
||||
expect(args[0]).to.be(i < 9 ? 'block' : 'last');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('calls emitted handlers asynchronously', (done) => {
|
||||
const listenerStub = sinon.stub();
|
||||
events.on('test', listenerStub);
|
||||
events.emit('test');
|
||||
eventsInstance.on('test', listenerStub);
|
||||
eventsInstance.emit('test');
|
||||
sinon.assert.notCalled(listenerStub);
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -46,11 +257,11 @@ describe('events', function () {
|
|||
|
||||
it('calling off after an emit that has not yet triggered the handler, will not call the handler', (done) => {
|
||||
const listenerStub = sinon.stub();
|
||||
events.on('test', listenerStub);
|
||||
events.emit('test');
|
||||
eventsInstance.on('test', listenerStub);
|
||||
eventsInstance.emit('test');
|
||||
// It's called asynchronously so it shouldn't be called yet.
|
||||
sinon.assert.notCalled(listenerStub);
|
||||
events.off('test', listenerStub);
|
||||
eventsInstance.off('test', listenerStub);
|
||||
|
||||
setTimeout(() => {
|
||||
sinon.assert.notCalled(listenerStub);
|
||||
|
|
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