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

This commit is contained in:
Fuyao Zhao 2019-03-28 20:03:17 -07:00
commit dc8dfbf6bd
427 changed files with 8036 additions and 6988 deletions

View file

@ -1,8 +1,5 @@
[[release-notes]]
= {kib} Release Notes
++++
<titleabbrev>Release Notes</titleabbrev>
++++
= Release Notes
[partintro]
--

View file

@ -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[]

View file

@ -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.

View file

@ -1,8 +1,5 @@
[[release-highlights]]
= {kib} Release Highlights
++++
<titleabbrev>Release Highlights</titleabbrev>
++++
= Release Highlights
[partintro]
--

View file

@ -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 servers certificate. For more information,
{watcher} to trust the {kib} servers 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:

View file

@ -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>>

View file

@ -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}.
+
--

View file

@ -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:

View file

@ -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",

View file

@ -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",

View 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');
}
};

View file

@ -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}`;
}
}

View file

@ -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);
});

View file

@ -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}`);

View file

@ -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",

View file

@ -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',

View file

@ -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);

View file

@ -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'),
}
});

View file

@ -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);

View file

@ -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);

View file

@ -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
];

View file

@ -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',
]
}

View file

@ -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>

View file

@ -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;
};

View file

@ -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>

View file

@ -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;
};

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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,

View file

@ -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);

View file

@ -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';

View file

@ -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';

View file

@ -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();
}

View file

@ -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';

View file

@ -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')

View file

@ -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

View file

@ -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' ]
]);
});
});

View file

@ -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;
}
};
};
}

View file

@ -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 = {

View file

@ -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>

View file

@ -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}

View file

@ -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"

View file

@ -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';

View file

@ -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';

View file

@ -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

View file

@ -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);

View file

@ -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 => {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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

View file

@ -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}

View file

@ -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} />

View file

@ -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)
};
}

View file

@ -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);
}

View file

@ -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)',

View file

@ -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'),
}

View file

@ -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);

View file

@ -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);

View file

@ -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'),
}

View file

@ -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);

View file

@ -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);

View file

@ -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>

View file

@ -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;
};

View file

@ -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'),
},

View file

@ -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;

View file

@ -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,
};

View file

@ -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();
});

View file

@ -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);

View file

@ -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'),
}
});

View file

@ -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);

View file

@ -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);

View file

@ -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'),
},
});

View file

@ -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);

View file

@ -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: [] }],

View file

@ -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'),
}
});

View file

@ -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);

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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({

View file

@ -59,6 +59,7 @@ export default function (kibana) {
visTypes: [
'plugins/timelion/vis'
],
interpreter: ['plugins/timelion/timelion_vis_fn'],
home: [
'plugins/timelion/register_feature'
],

View file

@ -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');

View file

@ -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);

View file

@ -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'),
},

View file

@ -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);

View file

@ -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;
};

View file

@ -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
}

View file

@ -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');
});

View file

@ -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',
],
})
);

View file

@ -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