Merge remote-tracking branch 'origin/master' into feature/merge-code
This commit is contained in:
commit
e7f9090060
|
@ -5,7 +5,7 @@ bower_components
|
||||||
/.es
|
/.es
|
||||||
/plugins
|
/plugins
|
||||||
/optimize
|
/optimize
|
||||||
/dlls
|
/built_assets
|
||||||
/src/fixtures/vislib/mock_data
|
/src/fixtures/vislib/mock_data
|
||||||
/src/ui/public/angular-bootstrap
|
/src/ui/public/angular-bootstrap
|
||||||
/src/ui/public/flot-charts
|
/src/ui/public/flot-charts
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,7 +9,7 @@ node_modules
|
||||||
!/src/dev/notice/__fixtures__/node_modules
|
!/src/dev/notice/__fixtures__/node_modules
|
||||||
trash
|
trash
|
||||||
/optimize
|
/optimize
|
||||||
/dlls
|
/built_assets
|
||||||
target
|
target
|
||||||
/build
|
/build
|
||||||
.jruby
|
.jruby
|
||||||
|
@ -44,4 +44,3 @@ package-lock.json
|
||||||
*.sublime-*
|
*.sublime-*
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
.tern-project
|
.tern-project
|
||||||
**/public/index.css
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ document.
|
||||||
`discover:sort:defaultOrder`:: Controls the default sort direction for time based index patterns in the Discover app.
|
`discover:sort:defaultOrder`:: Controls the default sort direction for time based index patterns in the Discover app.
|
||||||
`doc_table:highlight`:: Highlight results in Discover and Saved Searches Dashboard. Highlighting makes request slow when
|
`doc_table:highlight`:: Highlight results in Discover and Saved Searches Dashboard. Highlighting makes request slow when
|
||||||
working on big documents. Set this property to `false` to disable highlighting.
|
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
|
`search:includeFrozen`:: Will include {ref}/frozen-indices.html[frozen indices] in results if enabled. Searching through frozen indices
|
||||||
might increase the search time.
|
might increase the search time.
|
||||||
`courier:maxSegmentCount`:: Kibana splits requests in the Discover app into segments to limit the size of requests sent to
|
`courier:maxSegmentCount`:: Kibana splits requests in the Discover app into segments to limit the size of requests sent to
|
||||||
|
|
|
@ -94,13 +94,13 @@ https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless
|
||||||
=== Creating a Scripted Field
|
=== Creating a Scripted Field
|
||||||
To create a scripted field:
|
To create a scripted field:
|
||||||
|
|
||||||
. Go to *Settings > Indices*
|
. Go to *Management > Kibana > Index Patterns*
|
||||||
. Select the index pattern you want to add a scripted field to.
|
. Select the index pattern you want to add a scripted field to.
|
||||||
. Go to the pattern's *Scripted Fields* tab.
|
. Go to the pattern's *Scripted fields* tab.
|
||||||
. Click *Add Scripted Field*.
|
. Click *Add scripted field*.
|
||||||
. Enter a name for the scripted field.
|
. Enter a name for the scripted field.
|
||||||
. Enter the expression that you want to use to compute a value on the fly from your index data.
|
. Enter the expression that you want to use to compute a value on the fly from your index data.
|
||||||
. Click *Save Scripted Field*.
|
. Click *Create field*.
|
||||||
|
|
||||||
For more information about scripted fields in Elasticsearch, see
|
For more information about scripted fields in Elasticsearch, see
|
||||||
{ref}/modules-scripting.html[Scripting].
|
{ref}/modules-scripting.html[Scripting].
|
||||||
|
@ -110,9 +110,10 @@ For more information about scripted fields in Elasticsearch, see
|
||||||
=== Updating a Scripted Field
|
=== Updating a Scripted Field
|
||||||
To modify a scripted field:
|
To modify a scripted field:
|
||||||
|
|
||||||
. Go to *Settings > Indices*
|
. Go to *Management > Kibana > Index Patterns*
|
||||||
|
. Click the index pattern's *Scripted fields* tab.
|
||||||
. Click the *Edit* button for the scripted field you want to change.
|
. Click the *Edit* button for the scripted field you want to change.
|
||||||
. Make your changes and then click *Save Scripted Field* to update the field.
|
. Make your changes and then click *Save field* to update the field.
|
||||||
|
|
||||||
WARNING: Keep in mind that there's no built-in validation of a scripted field. If your scripts are buggy, you'll get
|
WARNING: Keep in mind that there's no built-in validation of a scripted field. If your scripts are buggy, you'll get
|
||||||
exceptions whenever you try to view the dynamically generated data.
|
exceptions whenever you try to view the dynamically generated data.
|
||||||
|
@ -122,6 +123,7 @@ exceptions whenever you try to view the dynamically generated data.
|
||||||
=== Deleting a Scripted Field
|
=== Deleting a Scripted Field
|
||||||
To delete a scripted field:
|
To delete a scripted field:
|
||||||
|
|
||||||
. Go to *Settings > Indices*
|
. Go to *Management > Kibana > Index Patterns*
|
||||||
|
. Click the index pattern's *Scripted fields* tab.
|
||||||
. Click the *Delete* button for the scripted field you want to remove.
|
. Click the *Delete* button for the scripted field you want to remove.
|
||||||
. Confirm that you really want to delete the field.
|
. Click *Delete* in the confirmation window.
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"extraPatterns": [
|
"extraPatterns": [
|
||||||
"build",
|
"build",
|
||||||
"optimize",
|
"optimize",
|
||||||
"dlls",
|
"built_assets",
|
||||||
".eslintcache"
|
".eslintcache"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export const CleanClientModulesOnDLLTask = {
|
||||||
];
|
];
|
||||||
|
|
||||||
// Resolve the client vendors dll manifest path
|
// Resolve the client vendors dll manifest path
|
||||||
const dllManifestPath = `${baseDir}/dlls/vendors.manifest.dll.json`;
|
const dllManifestPath = `${baseDir}/built_assets/dlls/vendors.manifest.dll.json`;
|
||||||
|
|
||||||
// Get dll entries filtering out the ones
|
// Get dll entries filtering out the ones
|
||||||
// from any whitelisted module
|
// from any whitelisted module
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const TranspileScssTask = {
|
||||||
const uiExports = collectUiExports(enabledPlugins);
|
const uiExports = collectUiExports(enabledPlugins);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bundles = await buildAll(uiExports.styleSheetPaths, log);
|
const bundles = await buildAll(uiExports.styleSheetPaths, log, build.resolvePath('built_assets/css'));
|
||||||
bundles.forEach(bundle => log.info(`Compiled SCSS: ${bundle.source}`));
|
bundles.forEach(bundle => log.info(`Compiled SCSS: ${bundle.source}`));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { message, line, file } = error;
|
const { message, line, file } = error;
|
||||||
|
|
|
@ -42,7 +42,7 @@ export async function generateNoticeFromSource({ productName, directory, log })
|
||||||
cwd: directory,
|
cwd: directory,
|
||||||
nodir: true,
|
nodir: true,
|
||||||
ignore: [
|
ignore: [
|
||||||
'{node_modules,build,target,dist,optimize,dlls}/**',
|
'{node_modules,build,target,dist,optimize,built_assets}/**',
|
||||||
'packages/*/{node_modules,build,target,dist}/**',
|
'packages/*/{node_modules,build,target,dist}/**',
|
||||||
'x-pack/{node_modules,build,target,dist,optimize}/**',
|
'x-pack/{node_modules,build,target,dist,optimize}/**',
|
||||||
'x-pack/packages/*/{node_modules,build,target,dist}/**',
|
'x-pack/packages/*/{node_modules,build,target,dist}/**',
|
||||||
|
|
|
@ -380,7 +380,8 @@ function discoverController(
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeFieldName = $scope.indexPattern.timeFieldName;
|
const timeFieldName = $scope.indexPattern.timeFieldName;
|
||||||
const fields = timeFieldName ? [timeFieldName, ...selectedFields] : selectedFields;
|
const hideTimeColumn = config.get('doc_table:hideTimeColumn');
|
||||||
|
const fields = (timeFieldName && !hideTimeColumn) ? [timeFieldName, ...selectedFields] : selectedFields;
|
||||||
return {
|
return {
|
||||||
searchFields: fields,
|
searchFields: fields,
|
||||||
selectFields: fields
|
selectFields: fields
|
||||||
|
|
|
@ -158,11 +158,12 @@ export function onPremInstructions(apmIndexPattern) {
|
||||||
index: apmIndexPattern,
|
index: apmIndexPattern,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
filter: {
|
should: [
|
||||||
exists: {
|
{ term: { 'processor.name': 'error' } },
|
||||||
field: 'processor.name',
|
{ term: { 'processor.name': 'transaction' } },
|
||||||
},
|
{ term: { 'processor.name': 'metric' } },
|
||||||
},
|
{ term: { 'processor.name': 'sourcemap' } },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -269,6 +269,16 @@ export function getUiSettingDefaults() {
|
||||||
}),
|
}),
|
||||||
category: ['discover'],
|
category: ['discover'],
|
||||||
},
|
},
|
||||||
|
'doc_table:hideTimeColumn': {
|
||||||
|
name: i18n.translate('kbn.advancedSettings.docTableHideTimeColumnTitle', {
|
||||||
|
defaultMessage: 'Hide \'Time\' column',
|
||||||
|
}),
|
||||||
|
value: false,
|
||||||
|
description: i18n.translate('kbn.advancedSettings.docTableHideTimeColumnText', {
|
||||||
|
defaultMessage: 'Hide the \'Time\' column in Discover and in all Saved Searches on Dashboards.',
|
||||||
|
}),
|
||||||
|
category: ['discover'],
|
||||||
|
},
|
||||||
'courier:maxSegmentCount': {
|
'courier:maxSegmentCount': {
|
||||||
name: i18n.translate('kbn.advancedSettings.courier.maxSegmentCountTitle', {
|
name: i18n.translate('kbn.advancedSettings.courier.maxSegmentCountTitle', {
|
||||||
defaultMessage: 'Maximum segment count',
|
defaultMessage: 'Maximum segment count',
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default (kibana) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`);
|
testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`);
|
||||||
testGlobs.push(`${plugin.publicDir}/**/*.css`);
|
testGlobs.push(`built_assets/css/plugins/${plugin.id}/**/*.css`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// add the modules from all of the apps
|
// add the modules from all of the apps
|
||||||
|
@ -80,7 +80,7 @@ export default (kibana) => {
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`);
|
testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`);
|
||||||
testGlobs.push(`${plugin.publicDir}/**/*.css`);
|
testGlobs.push(`built_assets/css/plugins/${plugin.id}/**/*.css`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,7 +185,7 @@ export class VegaParser {
|
||||||
delete this.spec.width;
|
delete this.spec.width;
|
||||||
delete this.spec.height;
|
delete this.spec.height;
|
||||||
} else {
|
} else {
|
||||||
this._onWarning(i18n.translate('vega.vegaParser.widthAndHeightParamsAreIngroredWithAutosizeFitWarningMessage', {
|
this._onWarning(i18n.translate('vega.vegaParser.widthAndHeightParamsAreIgnoredWithAutosizeFitWarningMessage', {
|
||||||
defaultMessage: 'The {widthParam} and {heightParam} params are ignored with {autosizeParam}',
|
defaultMessage: 'The {widthParam} and {heightParam} params are ignored with {autosizeParam}',
|
||||||
values: {
|
values: {
|
||||||
autosizeParam: 'autosize=fit',
|
autosizeParam: 'autosize=fit',
|
||||||
|
|
|
@ -42,7 +42,7 @@ VisTypesRegistryProvider.register((Private) => {
|
||||||
return VisFactory.createBaseVisualization({
|
return VisFactory.createBaseVisualization({
|
||||||
name: 'vega',
|
name: 'vega',
|
||||||
title: 'Vega',
|
title: 'Vega',
|
||||||
description: i18n.translate('vega.type.vegaВescription', {
|
description: i18n.translate('vega.type.vegaDescription', {
|
||||||
defaultMessage: 'Create custom visualizations using Vega and Vega-Lite',
|
defaultMessage: 'Create custom visualizations using Vega and Vega-Lite',
|
||||||
description: 'Vega and Vega-Lite are product names and should not be translated',
|
description: 'Vega and Vega-Lite are product names and should not be translated',
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -59,7 +59,7 @@ export function createBundlesRoute({ regularBundlesPath, dllBundlesPath, basePub
|
||||||
|
|
||||||
return [
|
return [
|
||||||
buildRouteForBundles(basePublicPath, '/bundles/', regularBundlesPath, fileHashCache),
|
buildRouteForBundles(basePublicPath, '/bundles/', regularBundlesPath, fileHashCache),
|
||||||
buildRouteForBundles(basePublicPath, '/dlls/', dllBundlesPath, fileHashCache),
|
buildRouteForBundles(basePublicPath, '/built_assets/dlls/', dllBundlesPath, fileHashCache),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
export function createProxyBundlesRoute({ host, port }) {
|
export function createProxyBundlesRoute({ host, port }) {
|
||||||
return [
|
return [
|
||||||
buildProxyRouteForBundles('/bundles/', host, port),
|
buildProxyRouteForBundles('/bundles/', host, port),
|
||||||
buildProxyRouteForBundles('/dlls/', host, port)
|
buildProxyRouteForBundles('/built_assets/dlls/', host, port)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export class DllCompiler {
|
||||||
dllExt: '.bundle.dll.js',
|
dllExt: '.bundle.dll.js',
|
||||||
manifestExt: '.manifest.dll.json',
|
manifestExt: '.manifest.dll.json',
|
||||||
styleExt: '.style.dll.css',
|
styleExt: '.style.dll.css',
|
||||||
outputPath: fromRoot('./dlls'),
|
outputPath: fromRoot('built_assets/dlls'),
|
||||||
publicPath: PUBLIC_PATH_PLACEHOLDER
|
publicPath: PUBLIC_PATH_PLACEHOLDER
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default async (kbnServer, server, config) => {
|
||||||
// bundles in a "middleware" style.
|
// bundles in a "middleware" style.
|
||||||
//
|
//
|
||||||
// the server listening on 5601 may be restarted a number of times, depending
|
// the server listening on 5601 may be restarted a number of times, depending
|
||||||
// on the watch setup managed by the cli. It proxies all bundles/* and dlls/*
|
// on the watch setup managed by the cli. It proxies all bundles/* and built_assets/dlls/*
|
||||||
// requests to the other server. The server on 5602 is long running, in order
|
// requests to the other server. The server on 5602 is long running, in order
|
||||||
// to prevent complete rebuilds of the optimize content.
|
// to prevent complete rebuilds of the optimize content.
|
||||||
const watch = config.get('optimize.watch');
|
const watch = config.get('optimize.watch');
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default async kbnServer => {
|
||||||
* while the optimizer is running
|
* while the optimizer is running
|
||||||
*
|
*
|
||||||
* server: this process runs the entire kibana server and proxies
|
* server: this process runs the entire kibana server and proxies
|
||||||
* all requests for /bundles/* or /dlls/* to the optmzr process
|
* all requests for /bundles/* or /built_assets/dlls/* to the optmzr process
|
||||||
*
|
*
|
||||||
* @param {string} process.env.kbnWorkerType
|
* @param {string} process.env.kbnWorkerType
|
||||||
*/
|
*/
|
||||||
|
|
5
src/server/sass/__fixtures__/index.scss
Normal file
5
src/server/sass/__fixtures__/index.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
foo {
|
||||||
|
bar {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,30 +23,24 @@ import fs from 'fs';
|
||||||
import sass from 'node-sass';
|
import sass from 'node-sass';
|
||||||
import autoprefixer from 'autoprefixer';
|
import autoprefixer from 'autoprefixer';
|
||||||
import postcss from 'postcss';
|
import postcss from 'postcss';
|
||||||
|
import mkdirp from 'mkdirp';
|
||||||
|
|
||||||
const renderSass = promisify(sass.render);
|
const renderSass = promisify(sass.render);
|
||||||
const writeFile = promisify(fs.writeFile);
|
const writeFile = promisify(fs.writeFile);
|
||||||
|
const mkdirpAsync = promisify(mkdirp);
|
||||||
|
|
||||||
export class Build {
|
export class Build {
|
||||||
constructor(source, log) {
|
constructor(source, log, targetPath) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.log = log;
|
this.log = log;
|
||||||
|
this.targetPath = targetPath;
|
||||||
this.includedFiles = [source];
|
this.includedFiles = [source];
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPath() {
|
|
||||||
const fileName = path.basename(this.source, path.extname(this.source)) + '.css';
|
|
||||||
return path.join(path.dirname(this.source), fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Glob based on source path
|
* Glob based on source path
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getGlob() {
|
|
||||||
return path.join(path.dirname(this.source), '**', '*.s{a,c}ss');
|
|
||||||
}
|
|
||||||
|
|
||||||
async buildIfIncluded(path) {
|
async buildIfIncluded(path) {
|
||||||
if (this.includedFiles && this.includedFiles.includes(path)) {
|
if (this.includedFiles && this.includedFiles.includes(path)) {
|
||||||
await this.build();
|
await this.build();
|
||||||
|
@ -61,11 +55,9 @@ export class Build {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async build() {
|
async build() {
|
||||||
const outFile = this.outputPath();
|
|
||||||
|
|
||||||
const rendered = await renderSass({
|
const rendered = await renderSass({
|
||||||
file: this.source,
|
file: this.source,
|
||||||
outFile,
|
outFile: this.targetPath,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
sourceMapEmbed: true,
|
sourceMapEmbed: true,
|
||||||
includePaths: [
|
includePaths: [
|
||||||
|
@ -78,7 +70,8 @@ export class Build {
|
||||||
|
|
||||||
this.includedFiles = rendered.stats.includedFiles;
|
this.includedFiles = rendered.stats.includedFiles;
|
||||||
|
|
||||||
await writeFile(outFile, prefixed.css);
|
await mkdirpAsync(path.dirname(this.targetPath));
|
||||||
|
await writeFile(this.targetPath, prefixed.css);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,34 +17,35 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import { resolve } from 'path';
|
||||||
import sass from 'node-sass';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
import del from 'del';
|
||||||
|
|
||||||
import { Build } from './build';
|
import { Build } from './build';
|
||||||
|
|
||||||
jest.mock('node-sass');
|
const TMP = resolve(__dirname, '__tmp__');
|
||||||
|
const FIXTURE = resolve(__dirname, '__fixtures__/index.scss');
|
||||||
|
|
||||||
describe('SASS builder', () => {
|
afterEach(async () => {
|
||||||
jest.mock('fs');
|
await del(TMP);
|
||||||
|
});
|
||||||
|
|
||||||
it('generates a glob', () => {
|
it('builds SASS', async () => {
|
||||||
const builder = new Build('/foo/style.sass');
|
const cssPath = resolve(TMP, 'style.css');
|
||||||
expect(builder.getGlob()).toEqual(path.join('/foo', '**', '*.s{a,c}ss'));
|
await (new Build(FIXTURE, {
|
||||||
});
|
info: () => {},
|
||||||
|
warn: () => {},
|
||||||
|
error: () => {},
|
||||||
|
}, cssPath)).build();
|
||||||
|
|
||||||
it('builds SASS', () => {
|
expect(readFileSync(cssPath, 'utf8').replace(/(\/\*# sourceMappingURL=).*( \*\/)/, '$1...$2'))
|
||||||
sass.render.mockImplementation(() => Promise.resolve(null, { css: 'test' }));
|
.toMatchInlineSnapshot(`
|
||||||
const builder = new Build('/foo/style.sass');
|
"foo bar {
|
||||||
builder.build();
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
const sassCall = sass.render.mock.calls[0][0];
|
display: -ms-flexbox;
|
||||||
expect(sassCall.file).toEqual('/foo/style.sass');
|
display: flex; }
|
||||||
expect(sassCall.outFile).toEqual(path.join('/foo', 'style.css'));
|
/*# sourceMappingURL=... */"
|
||||||
expect(sassCall.sourceMap).toBe(true);
|
`);
|
||||||
expect(sassCall.sourceMapEmbed).toBe(true);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('has an output file with a different extension', () => {
|
|
||||||
const builder = new Build('/foo/style.sass');
|
|
||||||
expect(builder.outputPath()).toEqual(path.join('/foo', 'style.css'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -17,16 +17,18 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
import { Build } from './build';
|
import { Build } from './build';
|
||||||
|
|
||||||
export async function buildAll(styleSheets = [], log) {
|
export async function buildAll(styleSheets, log, buildDir) {
|
||||||
const bundles = await Promise.all(styleSheets.map(async styleSheet => {
|
const bundles = await Promise.all(styleSheets.map(async styleSheet => {
|
||||||
|
|
||||||
if (!styleSheet.localPath.endsWith('.scss')) {
|
if (!styleSheet.localPath.endsWith('.scss')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bundle = new Build(styleSheet.localPath, log);
|
const bundle = new Build(styleSheet.localPath, log, resolve(buildDir, styleSheet.publicPath));
|
||||||
await bundle.build();
|
await bundle.build();
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IS_KIBANA_DISTRIBUTABLE } from '../../utils';
|
import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../../utils';
|
||||||
|
|
||||||
export async function sassMixin(kbnServer, server, config) {
|
export async function sassMixin(kbnServer, server, config) {
|
||||||
if (process.env.kbnWorkerType === 'optmzr') {
|
if (process.env.kbnWorkerType === 'optmzr') {
|
||||||
|
@ -45,7 +45,7 @@ export async function sassMixin(kbnServer, server, config) {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
scssBundles = await buildAll(kbnServer.uiExports.styleSheetPaths, log);
|
scssBundles = await buildAll(kbnServer.uiExports.styleSheetPaths, log, fromRoot('built_assets/css'));
|
||||||
|
|
||||||
scssBundles.forEach(bundle => {
|
scssBundles.forEach(bundle => {
|
||||||
bundle.includedFiles.forEach(file => trackedFiles.add(file));
|
bundle.includedFiles.forEach(file => trackedFiles.add(file));
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ElasticIndex fetchInfo handles v7 indices 1`] = `
|
||||||
|
Object {
|
||||||
|
"aliases": Object {
|
||||||
|
"foo": ".baz",
|
||||||
|
},
|
||||||
|
"exists": true,
|
||||||
|
"indexName": ".baz",
|
||||||
|
"mappings": Object {
|
||||||
|
"doc": Object {
|
||||||
|
"dynamic": "strict",
|
||||||
|
"properties": Object {
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`ElasticIndex write writes documents in bulk to the index 1`] = `
|
exports[`ElasticIndex write writes documents in bulk to the index 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"bulk",
|
"bulk",
|
||||||
|
|
|
@ -56,6 +56,23 @@ describe('ElasticIndex', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('handles v7 indices', async () => {
|
||||||
|
const callCluster = sinon.spy(async (path: string, { index }: any) => {
|
||||||
|
return {
|
||||||
|
[index]: {
|
||||||
|
aliases: { foo: index },
|
||||||
|
mappings: {
|
||||||
|
dynamic: 'strict',
|
||||||
|
properties: { a: 'b' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await Index.fetchInfo(callCluster, '.baz');
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test('fails if there are multiple root types', async () => {
|
test('fails if there are multiple root types', async () => {
|
||||||
const callCluster = sinon.spy(async (path: string, { index }: any) => {
|
const callCluster = sinon.spy(async (path: string, { index }: any) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -65,7 +65,7 @@ export async function fetchInfo(callCluster: CallCluster, index: string): Promis
|
||||||
|
|
||||||
const [indexName, indexInfo] = Object.entries(result)[0];
|
const [indexName, indexInfo] = Object.entries(result)[0];
|
||||||
|
|
||||||
return assertIsSupportedIndex({ ...indexInfo, exists: true, indexName });
|
return assertIsSupportedIndex({ ...normalizeV6AndV7(indexInfo), exists: true, indexName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -287,6 +287,26 @@ export async function claimAlias(
|
||||||
await callCluster('indices.refresh', { index });
|
await callCluster('indices.refresh', { index });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES7 removed the "doc" property from mappings. This function takes a v6 or v7
|
||||||
|
* index info object and returns an object in v6 form.
|
||||||
|
*/
|
||||||
|
function normalizeV6AndV7(indexInfo: FullIndexInfo) {
|
||||||
|
const mappings = indexInfo.mappings as any;
|
||||||
|
const isV7Index = !mappings.doc && mappings.dynamic && mappings.properties;
|
||||||
|
|
||||||
|
if (!isV7Index) {
|
||||||
|
return indexInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...indexInfo,
|
||||||
|
mappings: {
|
||||||
|
doc: mappings,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a rough check to ensure that the index being migrated satisfies at least
|
* This is a rough check to ensure that the index being migrated satisfies at least
|
||||||
* some rudimentary expectations. Past Kibana indices had multiple root documents, etc
|
* some rudimentary expectations. Past Kibana indices had multiple root documents, etc
|
||||||
|
@ -296,7 +316,7 @@ export async function claimAlias(
|
||||||
*
|
*
|
||||||
* @param {FullIndexInfo} indexInfo
|
* @param {FullIndexInfo} indexInfo
|
||||||
*/
|
*/
|
||||||
async function assertIsSupportedIndex(indexInfo: FullIndexInfo) {
|
function assertIsSupportedIndex(indexInfo: FullIndexInfo) {
|
||||||
const currentTypes = getTypes(indexInfo.mappings);
|
const currentTypes = getTypes(indexInfo.mappings);
|
||||||
const isV5Index = currentTypes.length > 1 || currentTypes[0] !== ROOT_TYPE;
|
const isV5Index = currentTypes.length > 1 || currentTypes[0] !== ROOT_TYPE;
|
||||||
if (isV5Index) {
|
if (isV5Index) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td width="1%"></td>
|
<td width="1%"></td>
|
||||||
<th
|
<th
|
||||||
ng-if="indexPattern.timeFieldName"
|
ng-if="indexPattern.timeFieldName && !hideTimeColumn"
|
||||||
data-test-subj="docTableHeaderField"
|
data-test-subj="docTableHeaderField"
|
||||||
scope="col"
|
scope="col"
|
||||||
>
|
>
|
||||||
|
|
|
@ -36,7 +36,9 @@ module.directive('kbnTableHeader', function (shortDotsFilter) {
|
||||||
onMoveColumn: '=?',
|
onMoveColumn: '=?',
|
||||||
},
|
},
|
||||||
template: headerHtml,
|
template: headerHtml,
|
||||||
controller: function ($scope) {
|
controller: function ($scope, config) {
|
||||||
|
$scope.hideTimeColumn = config.get('doc_table:hideTimeColumn');
|
||||||
|
|
||||||
$scope.isSortableColumn = function isSortableColumn(columnName) {
|
$scope.isSortableColumn = function isSortableColumn(columnName) {
|
||||||
return (
|
return (
|
||||||
!!$scope.indexPattern
|
!!$scope.indexPattern
|
||||||
|
|
|
@ -45,7 +45,7 @@ const MIN_LINE_LENGTH = 20;
|
||||||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl) {
|
module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl, config) {
|
||||||
const cellTemplate = _.template(noWhiteSpace(require('ui/doc_table/components/table_row/cell.html')));
|
const cellTemplate = _.template(noWhiteSpace(require('ui/doc_table/components/table_row/cell.html')));
|
||||||
const truncateByHeightTemplate = _.template(noWhiteSpace(require('ui/partials/truncate_by_height.html')));
|
const truncateByHeightTemplate = _.template(noWhiteSpace(require('ui/partials/truncate_by_height.html')));
|
||||||
|
|
||||||
|
@ -139,7 +139,8 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
|
||||||
];
|
];
|
||||||
|
|
||||||
const mapping = indexPattern.fields.byName;
|
const mapping = indexPattern.fields.byName;
|
||||||
if (indexPattern.timeFieldName) {
|
const hideTimeColumn = config.get('doc_table:hideTimeColumn');
|
||||||
|
if (indexPattern.timeFieldName && !hideTimeColumn) {
|
||||||
newHtmls.push(cellTemplate({
|
newHtmls.push(cellTemplate({
|
||||||
timefield: true,
|
timefield: true,
|
||||||
formatted: _displayField(row, indexPattern.timeFieldName),
|
formatted: _displayField(row, indexPattern.timeFieldName),
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
import { flatConcatAtType } from './reduce';
|
import { flatConcatAtType } from './reduce';
|
||||||
import { mapSpec, wrap } from './modify_reduce';
|
import { mapSpec, wrap } from './modify_reduce';
|
||||||
|
|
||||||
|
@ -46,16 +47,22 @@ function normalize(localPath, type, pluginSpec) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace the extension of localPath to be .css
|
||||||
|
// publicPath will always point to the css file
|
||||||
|
const localCssPath = localPath.slice(0, -extname.length) + '.css';
|
||||||
|
|
||||||
|
// update localPath to point to the .css file if it exists and
|
||||||
|
// the .scss path does not, which is the case for built plugins
|
||||||
|
if (extname === '.scss' && !existsSync(localPath) && existsSync(localCssPath)) {
|
||||||
|
localPath = localCssPath;
|
||||||
|
}
|
||||||
|
|
||||||
// get the path of the stylesheet relative to the public dir for the plugin
|
// get the path of the stylesheet relative to the public dir for the plugin
|
||||||
let relativePath = path.relative(publicDir, localPath);
|
let relativePath = path.relative(publicDir, localCssPath);
|
||||||
|
|
||||||
// replace back slashes on windows
|
// replace back slashes on windows
|
||||||
relativePath = relativePath.split('\\').join('/');
|
relativePath = relativePath.split('\\').join('/');
|
||||||
|
|
||||||
// replace the extension of relativePath to be .css
|
|
||||||
// publicPath will always point to the css file
|
|
||||||
relativePath = relativePath.slice(0, -extname.length) + '.css';
|
|
||||||
|
|
||||||
const publicPath = `plugins/${pluginSpec.getId()}/${relativePath}`;
|
const publicPath = `plugins/${pluginSpec.getId()}/${relativePath}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -64,4 +71,4 @@ function normalize(localPath, type, pluginSpec) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const styleSheetPaths = wrap(mapSpec(normalize), flatConcatAtType);
|
export const styleSheetPaths = wrap(mapSpec(normalize), flatConcatAtType);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { resolve } from 'path';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { AppBootstrap } from './bootstrap';
|
import { AppBootstrap } from './bootstrap';
|
||||||
import { mergeVariables } from './lib';
|
import { mergeVariables } from './lib';
|
||||||
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
export function uiRenderMixin(kbnServer, server, config) {
|
export function uiRenderMixin(kbnServer, server, config) {
|
||||||
function replaceInjectedVars(request, injectedVars) {
|
function replaceInjectedVars(request, injectedVars) {
|
||||||
|
@ -50,6 +51,9 @@ export function uiRenderMixin(kbnServer, server, config) {
|
||||||
// render all views from ./views
|
// render all views from ./views
|
||||||
server.setupViews(resolve(__dirname, 'views'));
|
server.setupViews(resolve(__dirname, 'views'));
|
||||||
|
|
||||||
|
// expose built css
|
||||||
|
server.exposeStaticDir('/built_assets/css/{path*}', fromRoot('built_assets/css'));
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
path: '/bundles/app/{id}/bootstrap.js',
|
path: '/bundles/app/{id}/bootstrap.js',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -63,12 +67,19 @@ export function uiRenderMixin(kbnServer, server, config) {
|
||||||
|
|
||||||
const basePath = config.get('server.basePath');
|
const basePath = config.get('server.basePath');
|
||||||
const regularBundlePath = `${basePath}/bundles`;
|
const regularBundlePath = `${basePath}/bundles`;
|
||||||
const dllBundlePath = `${basePath}/dlls`;
|
const dllBundlePath = `${basePath}/built_assets/dlls`;
|
||||||
const styleSheetPaths = [
|
const styleSheetPaths = [
|
||||||
`${dllBundlePath}/vendors.style.dll.css`,
|
`${dllBundlePath}/vendors.style.dll.css`,
|
||||||
`${regularBundlePath}/commons.style.css`,
|
`${regularBundlePath}/commons.style.css`,
|
||||||
`${regularBundlePath}/${app.getId()}.style.css`,
|
`${regularBundlePath}/${app.getId()}.style.css`,
|
||||||
].concat(kbnServer.uiExports.styleSheetPaths.map(path => `${basePath}/${path.publicPath}`).reverse());
|
...kbnServer.uiExports.styleSheetPaths
|
||||||
|
.map(path => (
|
||||||
|
path.localPath.endsWith('.scss')
|
||||||
|
? `${basePath}/built_assets/css/${path.publicPath}`
|
||||||
|
: `${basePath}/${path.publicPath}`
|
||||||
|
))
|
||||||
|
.reverse()
|
||||||
|
];
|
||||||
|
|
||||||
const bootstrap = new AppBootstrap({
|
const bootstrap = new AppBootstrap({
|
||||||
templateData: {
|
templateData: {
|
||||||
|
|
|
@ -85,17 +85,17 @@ module.exports = function (grunt) {
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: [
|
files: [
|
||||||
'http://localhost:5610/dlls/vendors.bundle.dll.js',
|
'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js',
|
||||||
'http://localhost:5610/bundles/tests.bundle.js',
|
'http://localhost:5610/bundles/tests.bundle.js',
|
||||||
|
|
||||||
'http://localhost:5610/dlls/vendors.style.dll.css',
|
'http://localhost:5610/built_assets/dlls/vendors.style.dll.css',
|
||||||
'http://localhost:5610/bundles/tests.style.css'
|
'http://localhost:5610/bundles/tests.style.css'
|
||||||
],
|
],
|
||||||
|
|
||||||
proxies: {
|
proxies: {
|
||||||
'/tests/': 'http://localhost:5610/tests/',
|
'/tests/': 'http://localhost:5610/tests/',
|
||||||
'/bundles/': 'http://localhost:5610/bundles/',
|
'/bundles/': 'http://localhost:5610/bundles/',
|
||||||
'/dlls/': 'http://localhost:5610/dlls/'
|
'/built_assets/dlls/': 'http://localhost:5610/built_assets/dlls/'
|
||||||
},
|
},
|
||||||
|
|
||||||
client: {
|
client: {
|
||||||
|
@ -176,10 +176,10 @@ module.exports = function (grunt) {
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
options: {
|
options: {
|
||||||
files: [
|
files: [
|
||||||
'http://localhost:5610/dlls/vendors.bundle.dll.js',
|
'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js',
|
||||||
`http://localhost:5610/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${n}`,
|
`http://localhost:5610/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${n}`,
|
||||||
|
|
||||||
'http://localhost:5610/dlls/vendors.style.dll.css',
|
'http://localhost:5610/built_assets/dlls/vendors.style.dll.css',
|
||||||
'http://localhost:5610/bundles/tests.style.css'
|
'http://localhost:5610/bundles/tests.style.css'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ const homeTabs: IHistoryTab[] = [
|
||||||
{
|
{
|
||||||
path: '/services',
|
path: '/services',
|
||||||
name: 'Services',
|
name: 'Services',
|
||||||
component: ServiceOverview
|
render: props => <ServiceOverview {...props} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/traces',
|
path: '/traces',
|
||||||
name: 'Traces',
|
name: 'Traces',
|
||||||
component: TraceOverview
|
render: props => <TraceOverview {...props} />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -39,14 +39,14 @@ exports[`Home component should render 1`] = `
|
||||||
tabs={
|
tabs={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
|
||||||
"name": "Services",
|
"name": "Services",
|
||||||
"path": "/services",
|
"path": "/services",
|
||||||
|
"render": [Function],
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
|
||||||
"name": "Traces",
|
"name": "Traces",
|
||||||
"path": "/traces",
|
"path": "/traces",
|
||||||
|
"render": [Function],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class ServiceDetailTabs extends React.Component<TabsProps> {
|
||||||
name: 'Transactions',
|
name: 'Transactions',
|
||||||
path: `/${serviceName}/transactions/${transactionTypes[0]}`,
|
path: `/${serviceName}/transactions/${transactionTypes[0]}`,
|
||||||
routePath: `/${serviceName}/transactions/:transactionType?`,
|
routePath: `/${serviceName}/transactions/:transactionType?`,
|
||||||
component: () => (
|
render: () => (
|
||||||
<TransactionOverview
|
<TransactionOverview
|
||||||
urlParams={urlParams}
|
urlParams={urlParams}
|
||||||
serviceTransactionTypes={transactionTypes}
|
serviceTransactionTypes={transactionTypes}
|
||||||
|
@ -36,7 +36,7 @@ export class ServiceDetailTabs extends React.Component<TabsProps> {
|
||||||
{
|
{
|
||||||
name: 'Errors',
|
name: 'Errors',
|
||||||
path: `/${serviceName}/errors`,
|
path: `/${serviceName}/errors`,
|
||||||
component: () => {
|
render: () => {
|
||||||
return (
|
return (
|
||||||
<ErrorGroupOverview urlParams={urlParams} location={location} />
|
<ErrorGroupOverview urlParams={urlParams} location={location} />
|
||||||
);
|
);
|
||||||
|
@ -45,7 +45,7 @@ export class ServiceDetailTabs extends React.Component<TabsProps> {
|
||||||
{
|
{
|
||||||
name: 'Metrics',
|
name: 'Metrics',
|
||||||
path: `/${serviceName}/metrics`,
|
path: `/${serviceName}/metrics`,
|
||||||
component: () => <ServiceMetrics urlParams={urlParams} />
|
render: () => <ServiceMetrics urlParams={urlParams} />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -90,26 +90,19 @@ export class MachineLearningFlyout extends Component<FlyoutProps, FlyoutState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public addErrorToast = () => {
|
public addErrorToast = () => {
|
||||||
const { location, urlParams } = this.props;
|
const { urlParams } = this.props;
|
||||||
const { serviceName = 'unknown', transactionType } = urlParams;
|
const { serviceName = 'unknown' } = urlParams;
|
||||||
|
|
||||||
if (!serviceName) {
|
if (!serviceName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toastNotifications.addWarning({
|
toastNotifications.addWarning({
|
||||||
title: 'Job already exists',
|
title: 'Job creation failed',
|
||||||
text: (
|
text: (
|
||||||
<p>
|
<p>
|
||||||
There's already a job running for anomaly detection on{' '}
|
Your current license may not allow for creating machine learning jobs,
|
||||||
{serviceName} ({transactionType}).{' '}
|
or this job may already exist.
|
||||||
<ViewMLJob
|
|
||||||
serviceName={serviceName}
|
|
||||||
transactionType={transactionType}
|
|
||||||
location={location}
|
|
||||||
>
|
|
||||||
View existing job
|
|
||||||
</ViewMLJob>
|
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,12 +70,13 @@ const traceListColumns: ITableColumn[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function TraceList({ items = [], noItemsMessage, isLoading }: Props) {
|
export function TraceList({ items = [], noItemsMessage, isLoading }: Props) {
|
||||||
return isLoading ? null : (
|
const noItems = isLoading ? null : noItemsMessage;
|
||||||
|
return (
|
||||||
<ManagedTable
|
<ManagedTable
|
||||||
columns={traceListColumns}
|
columns={traceListColumns}
|
||||||
items={items}
|
items={items}
|
||||||
initialSort={{ field: 'impact', direction: 'desc' }}
|
initialSort={{ field: 'impact', direction: 'desc' }}
|
||||||
noItemsMessage={noItemsMessage}
|
noItemsMessage={noItems}
|
||||||
initialPageSize={25}
|
initialPageSize={25}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ exports[`TransactionOverviewView should render with type filter controls 1`] = `
|
||||||
describedByIds={Array []}
|
describedByIds={Array []}
|
||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
hasEmptyLabelSpace={false}
|
hasEmptyLabelSpace={false}
|
||||||
|
label="Filter by type"
|
||||||
>
|
>
|
||||||
<EuiSelect
|
<EuiSelect
|
||||||
compressed={false}
|
compressed={false}
|
||||||
|
@ -16,11 +17,11 @@ exports[`TransactionOverviewView should render with type filter controls 1`] = `
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"text": "Filter by type: a",
|
"text": "a",
|
||||||
"value": "a",
|
"value": "a",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "Filter by type: b",
|
"text": "b",
|
||||||
"value": "b",
|
"value": "b",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -48,10 +48,10 @@ export class TransactionOverviewView extends React.Component<
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{serviceTransactionTypes.length > 1 ? (
|
{serviceTransactionTypes.length > 1 ? (
|
||||||
<EuiFormRow>
|
<EuiFormRow label="Filter by type">
|
||||||
<EuiSelect
|
<EuiSelect
|
||||||
options={serviceTransactionTypes.map(type => ({
|
options={serviceTransactionTypes.map(type => ({
|
||||||
text: `Filter by type: ${type}`,
|
text: `${type}`,
|
||||||
value: type
|
value: type
|
||||||
}))}
|
}))}
|
||||||
value={transactionType}
|
value={transactionType}
|
||||||
|
|
|
@ -40,17 +40,17 @@ describe('HistoryTabs', () => {
|
||||||
{
|
{
|
||||||
name: 'One',
|
name: 'One',
|
||||||
path: '/one',
|
path: '/one',
|
||||||
component: () => <Content name="one" />
|
render: props => <Content {...props} name="one" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Two',
|
name: 'Two',
|
||||||
path: '/two',
|
path: '/two',
|
||||||
component: () => <Content name="two" />
|
render: () => <Content name="two" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Three',
|
name: 'Three',
|
||||||
path: '/three',
|
path: '/three',
|
||||||
component: () => <Content name="three" />
|
render: () => <Content name="three" />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -55,19 +55,19 @@ exports[`HistoryTabs should render correctly 1`] = `
|
||||||
size="l"
|
size="l"
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
component={[Function]}
|
|
||||||
key="/one"
|
key="/one"
|
||||||
path="/one"
|
path="/one"
|
||||||
|
render={[Function]}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
component={[Function]}
|
|
||||||
key="/two"
|
key="/two"
|
||||||
path="/two"
|
path="/two"
|
||||||
|
render={[Function]}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
component={[Function]}
|
|
||||||
key="/three"
|
key="/three"
|
||||||
path="/three"
|
path="/three"
|
||||||
|
render={[Function]}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -17,7 +17,7 @@ export interface IHistoryTab {
|
||||||
path: string;
|
path: string;
|
||||||
routePath?: string;
|
routePath?: string;
|
||||||
name: React.ReactNode;
|
name: React.ReactNode;
|
||||||
component?: React.SFC | React.ComponentClass;
|
render?: (props: RouteComponentProps) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryTabsProps extends RouteComponentProps {
|
export interface HistoryTabsProps extends RouteComponentProps {
|
||||||
|
@ -51,10 +51,10 @@ const HistoryTabsWithoutRouter = ({
|
||||||
</EuiTabs>
|
</EuiTabs>
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
{tabs.map(tab =>
|
{tabs.map(tab =>
|
||||||
tab.component ? (
|
tab.render ? (
|
||||||
<Route
|
<Route
|
||||||
path={tab.routePath || tab.path}
|
path={tab.routePath || tab.path}
|
||||||
component={tab.component}
|
render={tab.render}
|
||||||
key={tab.path}
|
key={tab.path}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { PROCESSOR_NAME } from '../../../common/constants';
|
import { PROCESSOR_NAME } from '../../../common/constants';
|
||||||
|
|
||||||
|
// Note: this logic is duplicated in tutorials/apm/envs/on_prem
|
||||||
export async function getAgentStatus({ setup }) {
|
export async function getAgentStatus({ setup }) {
|
||||||
const { client, config } = setup;
|
const { client, config } = setup;
|
||||||
|
|
||||||
|
@ -18,11 +19,12 @@ export async function getAgentStatus({ setup }) {
|
||||||
size: 0,
|
size: 0,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
filter: {
|
should: [
|
||||||
exists: {
|
{ term: { [PROCESSOR_NAME]: 'error' } },
|
||||||
field: PROCESSOR_NAME
|
{ term: { [PROCESSOR_NAME]: 'transaction' } },
|
||||||
}
|
{ term: { [PROCESSOR_NAME]: 'metric' } },
|
||||||
}
|
{ term: { [PROCESSOR_NAME]: 'sourcemap' } }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Note: this logic is duplicated in tutorials/apm/envs/on_prem
|
||||||
export async function getServerStatus({ setup }) {
|
export async function getServerStatus({ setup }) {
|
||||||
const { client, config } = setup;
|
const { client, config } = setup;
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ describe('alterColumn', () => {
|
||||||
|
|
||||||
it('throws when converting to an invalid type', () => {
|
it('throws when converting to an invalid type', () => {
|
||||||
expect(() => fn(testTable, { column: 'name', type: 'foo' })).to.throwException(e => {
|
expect(() => fn(testTable, { column: 'name', type: 'foo' })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Cannot convert to foo');
|
expect(e.message).to.be(`Cannot convert to 'foo'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,7 +59,7 @@ describe('axisConfig', () => {
|
||||||
expect(fn)
|
expect(fn)
|
||||||
.withArgs(testTable, { position: 'foo' })
|
.withArgs(testTable, { position: 'foo' })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be('Invalid position foo');
|
expect(e.message).to.be(`Invalid position: 'foo'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -83,7 +83,7 @@ describe('axisConfig', () => {
|
||||||
.withArgs(testTable, { min: 'foo' })
|
.withArgs(testTable, { min: 'foo' })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be(
|
expect(e.message).to.be(
|
||||||
`Invalid date string 'foo' found. 'min' must be a number, date in ms, or ISO8601 date string`
|
`Invalid date string: 'foo'. 'min' must be a number, date in ms, or ISO8601 date string`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -108,7 +108,7 @@ describe('axisConfig', () => {
|
||||||
.withArgs(testTable, { max: '20/02/17' })
|
.withArgs(testTable, { max: '20/02/17' })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be(
|
expect(e.message).to.be(
|
||||||
`Invalid date string '20/02/17' found. 'max' must be a number, date in ms, or ISO8601 date string`
|
`Invalid date string: '20/02/17'. 'max' must be a number, date in ms, or ISO8601 date string`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,10 +24,14 @@ describe('compare', () => {
|
||||||
|
|
||||||
it('throws when invalid op is provided', () => {
|
it('throws when invalid op is provided', () => {
|
||||||
expect(() => fn(1, { op: 'boo', to: 2 })).to.throwException(e => {
|
expect(() => fn(1, { op: 'boo', to: 2 })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Invalid compare operator. Use eq, ne, lt, gt, lte, or gte.');
|
expect(e.message).to.be(
|
||||||
|
`Invalid compare operator: 'boo'. Use eq, ne, lt, gt, lte, or gte.`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(() => fn(1, { op: 'boo' })).to.throwException(e => {
|
expect(() => fn(1, { op: 'boo' })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Invalid compare operator. Use eq, ne, lt, gt, lte, or gte.');
|
expect(e.message).to.be(
|
||||||
|
`Invalid compare operator: 'boo'. Use eq, ne, lt, gt, lte, or gte.`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -105,7 +105,7 @@ describe('font', () => {
|
||||||
|
|
||||||
it('throws when provided an invalid weight', () => {
|
it('throws when provided an invalid weight', () => {
|
||||||
expect(() => fn(null, { weight: 'foo' })).to.throwException(e => {
|
expect(() => fn(null, { weight: 'foo' })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Invalid font weight: foo');
|
expect(e.message).to.be(`Invalid font weight: 'foo'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -175,7 +175,7 @@ describe('font', () => {
|
||||||
expect(fn)
|
expect(fn)
|
||||||
.withArgs(null, { align: 'foo' })
|
.withArgs(null, { align: 'foo' })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be('Invalid text alignment: foo');
|
expect(e.message).to.be(`Invalid text alignment: 'foo'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,7 @@ describe('getCell', () => {
|
||||||
|
|
||||||
it('throws when invalid column is provided', () => {
|
it('throws when invalid column is provided', () => {
|
||||||
expect(() => fn(testTable, { column: 'foo' })).to.throwException(e => {
|
expect(() => fn(testTable, { column: 'foo' })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Column not found: foo');
|
expect(e.message).to.be(`Column not found: 'foo'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -66,15 +66,15 @@ describe('getCell', () => {
|
||||||
const invalidRow = testTable.rows.length;
|
const invalidRow = testTable.rows.length;
|
||||||
|
|
||||||
expect(() => fn(testTable, { column: 'name', row: invalidRow })).to.throwException(e => {
|
expect(() => fn(testTable, { column: 'name', row: invalidRow })).to.throwException(e => {
|
||||||
expect(e.message).to.be(`Row not found: ${invalidRow}`);
|
expect(e.message).to.be(`Row not found: '${invalidRow}'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(() => fn(emptyTable, { column: 'foo' })).to.throwException(e => {
|
expect(() => fn(emptyTable, { column: 'foo' })).to.throwException(e => {
|
||||||
expect(e.message).to.be('Row not found: 0');
|
expect(e.message).to.be(`Row not found: '0'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(() => fn(emptyTable)).to.throwException(e => {
|
expect(() => fn(emptyTable)).to.throwException(e => {
|
||||||
expect(e.message).to.be('Row not found: 0');
|
expect(e.message).to.be(`Row not found: '0'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,12 +82,12 @@ describe('ply', () => {
|
||||||
it('throws when by is an invalid column', () => {
|
it('throws when by is an invalid column', () => {
|
||||||
expect(() => fn(testTable, { by: [''], expression: [averagePrice] })).to.throwException(
|
expect(() => fn(testTable, { by: [''], expression: [averagePrice] })).to.throwException(
|
||||||
e => {
|
e => {
|
||||||
expect(e.message).to.be('No such column: ');
|
expect(e.message).to.be(`Column not found: ''`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(() => fn(testTable, { by: ['foo'], expression: [averagePrice] })).to.throwException(
|
expect(() => fn(testTable, { by: ['foo'], expression: [averagePrice] })).to.throwException(
|
||||||
e => {
|
e => {
|
||||||
expect(e.message).to.be('No such column: foo');
|
expect(e.message).to.be(`Column not found: 'foo'`);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('progress', () => {
|
||||||
expect(fn)
|
expect(fn)
|
||||||
.withArgs(3)
|
.withArgs(3)
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be('Context must be between 0 and 1');
|
expect(e.message).to.be(`Invalid value: '3'. Value must be between 0 and 1`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ describe('progress', () => {
|
||||||
expect(fn)
|
expect(fn)
|
||||||
.withArgs(value, { max: -0.5 })
|
.withArgs(value, { max: -0.5 })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be(`'max' must be greater than 0`);
|
expect(e.message).to.be(`Invalid max value: '-0.5'. 'max' must be greater than 0`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('revealImage', () => {
|
||||||
origin: 'top',
|
origin: 'top',
|
||||||
})
|
})
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be.equal('input must be between 0 and 1');
|
expect(e.message).to.be.equal(`Invalid value: '10'. Percentage must be between 0 and 1`);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fn)
|
expect(fn)
|
||||||
|
@ -39,7 +39,9 @@ describe('revealImage', () => {
|
||||||
origin: 'top',
|
origin: 'top',
|
||||||
})
|
})
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be.equal('input must be between 0 and 1');
|
expect(e.message).to.be.equal(
|
||||||
|
`Invalid value: '-0.1'. Percentage must be between 0 and 1`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,7 +88,7 @@ describe('timefilter', () => {
|
||||||
|
|
||||||
it('throws when provided an invalid date string', () => {
|
it('throws when provided an invalid date string', () => {
|
||||||
expect(() => fn(emptyFilter, { from: '2018-13-42T15:00:00.950Z' })).to.throwException(e => {
|
expect(() => fn(emptyFilter, { from: '2018-13-42T15:00:00.950Z' })).to.throwException(e => {
|
||||||
expect(e.message).to.be.equal('Invalid date/time string 2018-13-42T15:00:00.950Z');
|
expect(e.message).to.be.equal(`Invalid date/time string: '2018-13-42T15:00:00.950Z'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,7 +74,7 @@ export const alterColumn = () => ({
|
||||||
case 'null':
|
case 'null':
|
||||||
return () => null;
|
return () => null;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Cannot convert to ${type}`);
|
throw new Error(`Cannot convert to '${type}'`);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export const axisConfig = () => ({
|
||||||
fn: (context, args) => {
|
fn: (context, args) => {
|
||||||
const positions = ['top', 'bottom', 'left', 'right', ''];
|
const positions = ['top', 'bottom', 'left', 'right', ''];
|
||||||
if (!positions.includes(args.position)) {
|
if (!positions.includes(args.position)) {
|
||||||
throw new Error(`Invalid position ${args.position}`);
|
throw new Error(`Invalid position: '${args.position}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const min = typeof args.min === 'string' ? moment.utc(args.min).valueOf() : args.min;
|
const min = typeof args.min === 'string' ? moment.utc(args.min).valueOf() : args.min;
|
||||||
|
@ -51,16 +51,16 @@ export const axisConfig = () => ({
|
||||||
|
|
||||||
if (min != null && isNaN(min)) {
|
if (min != null && isNaN(min)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid date string '${
|
`Invalid date string: '${
|
||||||
args.min
|
args.min
|
||||||
}' found. 'min' must be a number, date in ms, or ISO8601 date string`
|
}'. 'min' must be a number, date in ms, or ISO8601 date string`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (max != null && isNaN(max)) {
|
if (max != null && isNaN(max)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid date string '${
|
`Invalid date string: '${
|
||||||
args.max
|
args.max
|
||||||
}' found. 'max' must be a number, date in ms, or ISO8601 date string`
|
}'. 'max' must be a number, date in ms, or ISO8601 date string`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const compare = () => ({
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid compare operator. Use eq, ne, lt, gt, lte, or gte.');
|
throw new Error(`Invalid compare operator: '${op}'. Use eq, ne, lt, gt, lte, or gte.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -80,10 +80,10 @@ export const font = () => ({
|
||||||
},
|
},
|
||||||
fn: (context, args) => {
|
fn: (context, args) => {
|
||||||
if (!weights.includes(args.weight)) {
|
if (!weights.includes(args.weight)) {
|
||||||
throw new Error(`Invalid font weight: ${args.weight}`);
|
throw new Error(`Invalid font weight: '${args.weight}'`);
|
||||||
}
|
}
|
||||||
if (!alignments.includes(args.align)) {
|
if (!alignments.includes(args.align)) {
|
||||||
throw new Error(`Invalid text alignment: ${args.align}`);
|
throw new Error(`Invalid text alignment: '${args.align}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the line height shouldn't ever be lower than the size
|
// the line height shouldn't ever be lower than the size
|
||||||
|
|
|
@ -26,14 +26,14 @@ export const getCell = () => ({
|
||||||
fn: (context, args) => {
|
fn: (context, args) => {
|
||||||
const row = context.rows[args.row];
|
const row = context.rows[args.row];
|
||||||
if (!row) {
|
if (!row) {
|
||||||
throw new Error(`Row not found: ${args.row}`);
|
throw new Error(`Row not found: '${args.row}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { column = context.columns[0].name } = args;
|
const { column = context.columns[0].name } = args;
|
||||||
const value = row[column];
|
const value = row[column];
|
||||||
|
|
||||||
if (typeof value === 'undefined') {
|
if (typeof value === 'undefined') {
|
||||||
throw new Error(`Column not found: ${column}`);
|
throw new Error(`Column not found: '${column}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const ply = () => ({
|
||||||
byColumns = args.by.map(by => {
|
byColumns = args.by.map(by => {
|
||||||
const column = context.columns.find(column => column.name === by);
|
const column = context.columns.find(column => column.name === by);
|
||||||
if (!column) {
|
if (!column) {
|
||||||
throw new Error(`No such column: ${by}`);
|
throw new Error(`Column not found: '${by}'`);
|
||||||
}
|
}
|
||||||
return column;
|
return column;
|
||||||
});
|
});
|
||||||
|
|
|
@ -72,10 +72,10 @@ export const progress = () => ({
|
||||||
},
|
},
|
||||||
fn: (value, args) => {
|
fn: (value, args) => {
|
||||||
if (args.max <= 0) {
|
if (args.max <= 0) {
|
||||||
throw new Error(`'max' must be greater than 0`);
|
throw new Error(`Invalid max value: '${args.max}'. 'max' must be greater than 0`);
|
||||||
}
|
}
|
||||||
if (value > args.max || value < 0) {
|
if (value > args.max || value < 0) {
|
||||||
throw new Error(`Context must be between 0 and ${args.max}`);
|
throw new Error(`Invalid value: '${value}'. Value must be between 0 and ${args.max}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = '';
|
let label = '';
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const revealImage = () => ({
|
||||||
},
|
},
|
||||||
fn: (percent, args) => {
|
fn: (percent, args) => {
|
||||||
if (percent > 1 || percent < 0) {
|
if (percent > 1 || percent < 0) {
|
||||||
throw new Error('input must be between 0 and 1');
|
throw new Error(`Invalid value: '${percent}'. Percentage must be between 0 and 1`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const timefilter = () => ({
|
||||||
|
|
||||||
const moment = dateMath.parse(str);
|
const moment = dateMath.parse(str);
|
||||||
if (!moment || !moment.isValid()) {
|
if (!moment || !moment.isValid()) {
|
||||||
throw new Error(`Invalid date/time string ${str}`);
|
throw new Error(`Invalid date/time string: '${str}'`);
|
||||||
}
|
}
|
||||||
return moment.toISOString();
|
return moment.toISOString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe('demodata', () => {
|
||||||
expect(fn)
|
expect(fn)
|
||||||
.withArgs(null, { type: 'foo' })
|
.withArgs(null, { type: 'foo' })
|
||||||
.to.throwException(e => {
|
.to.throwException(e => {
|
||||||
expect(e.message).to.be("Invalid data set: foo, use 'ci' or 'shirts'.");
|
expect(e.message).to.be("Invalid data set: 'foo', use 'ci' or 'shirts'.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,5 +15,5 @@ export function getDemoRows(arg) {
|
||||||
if (arg === 'shirts') {
|
if (arg === 'shirts') {
|
||||||
return cloneDeep(shirts);
|
return cloneDeep(shirts);
|
||||||
}
|
}
|
||||||
throw new Error(`Invalid data set: ${arg}, use 'ci' or 'shirts'.`);
|
throw new Error(`Invalid data set: '${arg}', use 'ci' or 'shirts'.`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,52 +6,53 @@
|
||||||
|
|
||||||
export const americanTypewriter = {
|
export const americanTypewriter = {
|
||||||
label: 'American Typewriter',
|
label: 'American Typewriter',
|
||||||
value: `'American Typewriter', 'Courier New', Courier, Monaco, mono`,
|
value: "'American Typewriter', 'Courier New', Courier, Monaco, mono",
|
||||||
};
|
};
|
||||||
export const arial = { label: 'Arial', value: `Arial, sans-serif` };
|
export const arial = { label: 'Arial', value: 'Arial, sans-serif' };
|
||||||
export const baskerville = {
|
export const baskerville = {
|
||||||
label: 'Baskerville',
|
label: 'Baskerville',
|
||||||
value: `Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif`,
|
value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif",
|
||||||
};
|
};
|
||||||
export const bookAntiqua = {
|
export const bookAntiqua = {
|
||||||
label: 'Book Antiqua',
|
label: 'Book Antiqua',
|
||||||
value: `'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif`,
|
value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif",
|
||||||
};
|
};
|
||||||
export const brushScript = {
|
export const brushScript = {
|
||||||
label: 'Brush Script',
|
label: 'Brush Script',
|
||||||
value: `'Brush Script MT', 'Comic Sans', sans-serif`,
|
value: "'Brush Script MT', 'Comic Sans', sans-serif",
|
||||||
};
|
};
|
||||||
export const chalkboard = { label: 'Chalkboard', value: `Chalkboard, 'Comic Sans', sans-serif` };
|
export const chalkboard = { label: 'Chalkboard', value: "Chalkboard, 'Comic Sans', sans-serif" };
|
||||||
export const didot = {
|
export const didot = {
|
||||||
label: 'Didot',
|
label: 'Didot',
|
||||||
value: `Didot, Georgia, Garamond, 'Times New Roman', Times, serif`,
|
value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif",
|
||||||
};
|
};
|
||||||
export const futura = { label: 'Futura', value: `Futura, Impact, Helvetica, Arial, sans-serif` };
|
export const futura = { label: 'Futura', value: 'Futura, Impact, Helvetica, Arial, sans-serif' };
|
||||||
export const gillSans = {
|
export const gillSans = {
|
||||||
label: 'Gill Sans',
|
label: 'Gill Sans',
|
||||||
value: `'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif`,
|
value:
|
||||||
|
"'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
|
||||||
};
|
};
|
||||||
export const helveticaNeue = {
|
export const helveticaNeue = {
|
||||||
label: 'Helvetica Neue',
|
label: 'Helvetica Neue',
|
||||||
value: `'Helvetica Neue', Helvetica, Arial, sans-serif`,
|
value: "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||||
};
|
};
|
||||||
export const hoeflerText = {
|
export const hoeflerText = {
|
||||||
label: 'Hoefler Text',
|
label: 'Hoefler Text',
|
||||||
value: `'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif`,
|
value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif",
|
||||||
};
|
};
|
||||||
export const lucidaGrande = {
|
export const lucidaGrande = {
|
||||||
label: 'Lucida Grande',
|
label: 'Lucida Grande',
|
||||||
value: `'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif`,
|
value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif",
|
||||||
};
|
};
|
||||||
export const myriad = { label: 'Myriad', value: `Myriad, Helvetica, Arial, sans-serif` };
|
export const myriad = { label: 'Myriad', value: 'Myriad, Helvetica, Arial, sans-serif' };
|
||||||
export const openSans = { label: 'Open Sans', value: `'Open Sans', Helvetica, Arial, sans-serif` };
|
export const openSans = { label: 'Open Sans', value: "'Open Sans', Helvetica, Arial, sans-serif" };
|
||||||
export const optima = {
|
export const optima = {
|
||||||
label: 'Optima',
|
label: 'Optima',
|
||||||
value: `Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif`,
|
value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif",
|
||||||
};
|
};
|
||||||
export const palatino = {
|
export const palatino = {
|
||||||
label: 'Palatino',
|
label: 'Palatino',
|
||||||
value: `Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif`,
|
value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif",
|
||||||
};
|
};
|
||||||
export const fonts = [
|
export const fonts = [
|
||||||
americanTypewriter,
|
americanTypewriter,
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const routes = [
|
||||||
notify.error(err, { title: `Couldn't create workpad` });
|
notify.error(err, { title: `Couldn't create workpad` });
|
||||||
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
||||||
// https://github.com/elastic/kibana/issues/20277
|
// https://github.com/elastic/kibana/issues/20277
|
||||||
if (err.response.status === 403) {
|
if (err.response && err.response.status === 403) {
|
||||||
dispatch(setCanUserWrite(false));
|
dispatch(setCanUserWrite(false));
|
||||||
}
|
}
|
||||||
router.redirectTo('home');
|
router.redirectTo('home');
|
||||||
|
@ -61,7 +61,7 @@ export const routes = [
|
||||||
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
||||||
// https://github.com/elastic/kibana/issues/20277
|
// https://github.com/elastic/kibana/issues/20277
|
||||||
workpadService.update(params.id, fetchedWorkpad).catch(err => {
|
workpadService.update(params.id, fetchedWorkpad).catch(err => {
|
||||||
if (err.response.status === 403) {
|
if (err.response && err.response.status === 403) {
|
||||||
dispatch(setCanUserWrite(false));
|
dispatch(setCanUserWrite(false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class Clipboard extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="canvas_clipboard" onClick={this.onClick}>
|
<div className="canvasClipboard" onClick={this.onClick}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.canvasClipboard {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -10,13 +10,13 @@ import { render } from 'enzyme';
|
||||||
import { Download } from '../';
|
import { Download } from '../';
|
||||||
|
|
||||||
describe('<Download />', () => {
|
describe('<Download />', () => {
|
||||||
it('has canvas_download class', () => {
|
it('has canvasDownload class', () => {
|
||||||
const wrapper = render(
|
const wrapper = render(
|
||||||
<Download fileName="hello" content="world">
|
<Download fileName="hello" content="world">
|
||||||
<button>Download it</button>
|
<button>Download it</button>
|
||||||
</Download>
|
</Download>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.hasClass('canvas_download')).to.be.ok;
|
expect(wrapper.hasClass('canvasDownload')).to.be.ok;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class Download extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="canvas_download" onClick={this.onClick}>
|
<div className="canvasDownload" onClick={this.onClick}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { getWorkpad, getPages } from '../../state/selectors/workpad';
|
||||||
import { getReportingBrowserType } from '../../state/selectors/app';
|
import { getReportingBrowserType } from '../../state/selectors/app';
|
||||||
import { notify } from '../../lib/notify';
|
import { notify } from '../../lib/notify';
|
||||||
import { getWindow } from '../../lib/get_window';
|
import { getWindow } from '../../lib/get_window';
|
||||||
|
import { downloadWorkpad } from '../../lib/download_workpad';
|
||||||
import { WorkpadExport as Component } from './workpad_export';
|
import { WorkpadExport as Component } from './workpad_export';
|
||||||
import { getPdfUrl, createPdf } from './utils';
|
import { getPdfUrl, createPdf } from './utils';
|
||||||
|
|
||||||
|
@ -43,29 +44,34 @@ export const WorkpadExport = compose(
|
||||||
throw new Error(`Unknown export type: ${type}`);
|
throw new Error(`Unknown export type: ${type}`);
|
||||||
},
|
},
|
||||||
onCopy: type => {
|
onCopy: type => {
|
||||||
if (type === 'pdf') {
|
switch (type) {
|
||||||
return notify.info('The PDF generation URL was copied to your clipboard.');
|
case 'pdf':
|
||||||
|
return notify.info('The PDF generation URL was copied to your clipboard.');
|
||||||
|
case 'reportingConfig':
|
||||||
|
return notify.info(`Copied reporting configuration to clipboard`);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown export type: ${type}`);
|
throw new Error(`Unknown export type: ${type}`);
|
||||||
},
|
},
|
||||||
onExport: type => {
|
onExport: type => {
|
||||||
if (type === 'pdf') {
|
switch (type) {
|
||||||
return createPdf(workpad, { pageCount })
|
case 'pdf':
|
||||||
.then(({ data }) => {
|
return createPdf(workpad, { pageCount })
|
||||||
notify.info('Exporting PDF. You can track the progress in Management.', {
|
.then(({ data }) => {
|
||||||
title: `PDF export of workpad '${workpad.name}'`,
|
notify.info('Exporting PDF. You can track the progress in Management.', {
|
||||||
|
title: `PDF export of workpad '${workpad.name}'`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// register the job so a completion notification shows up when it's ready
|
||||||
|
jobCompletionNotifications.add(data.job.id);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
notify.error(err, { title: `Failed to create PDF for '${workpad.name}'` });
|
||||||
});
|
});
|
||||||
|
case 'json':
|
||||||
// register the job so a completion notification shows up when it's ready
|
return downloadWorkpad(workpad.id);
|
||||||
jobCompletionNotifications.add(data.job.id);
|
default:
|
||||||
})
|
throw new Error(`Unknown export type: ${type}`);
|
||||||
.catch(err => {
|
|
||||||
notify.error(err, { title: `Failed to create PDF for '${workpad.name}'` });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown export type: ${type}`);
|
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
)(Component);
|
)(Component);
|
||||||
|
|
|
@ -9,12 +9,12 @@ import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiButtonIcon,
|
EuiButtonIcon,
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiCodeBlock,
|
EuiCodeBlock,
|
||||||
EuiHorizontalRule,
|
EuiCode,
|
||||||
EuiFormRow,
|
EuiContextMenu,
|
||||||
|
EuiIcon,
|
||||||
|
EuiText,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { Popover } from '../popover';
|
import { Popover } from '../popover';
|
||||||
import { Clipboard } from '../clipboard';
|
import { Clipboard } from '../clipboard';
|
||||||
|
@ -27,81 +27,155 @@ export class WorkpadExport extends React.PureComponent {
|
||||||
getExportUrl: PropTypes.func.isRequired,
|
getExportUrl: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
anchorElement = React.createRef();
|
||||||
|
|
||||||
|
flattenPanelTree(tree, array = []) {
|
||||||
|
array.push(tree);
|
||||||
|
|
||||||
|
if (tree.items) {
|
||||||
|
tree.items.forEach(item => {
|
||||||
|
if (item.panel) {
|
||||||
|
this.flattenPanelTree(item.panel, array);
|
||||||
|
item.panel = item.panel.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
exportPdf = () => {
|
exportPdf = () => {
|
||||||
this.props.onExport('pdf');
|
this.props.onExport('pdf');
|
||||||
};
|
};
|
||||||
|
|
||||||
renderControls = closePopover => {
|
downloadWorkpad = () => {
|
||||||
|
this.props.onExport('json');
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPDFControls = closePopover => {
|
||||||
const pdfUrl = this.props.getExportUrl('pdf');
|
const pdfUrl = this.props.getExportUrl('pdf');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="canvasWorkpadExport__panelContent">
|
||||||
<EuiFlexGroup justifyContent="spaceAround">
|
<EuiText size="s">
|
||||||
<EuiFlexItem grow>
|
<p>PDFs can take a minute or two to generate based upon the size of your workpad</p>
|
||||||
<EuiFormRow label="Click below to create a PDF. You'll be notified when the export is complete">
|
</EuiText>
|
||||||
<EuiButton
|
<EuiSpacer size="s" />
|
||||||
onClick={() => {
|
|
||||||
this.exportPdf();
|
|
||||||
closePopover();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Export as PDF
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiHorizontalRule size="half" />
|
|
||||||
<EuiFormRow label="To generate a PDF from a script or with Watcher, use this URL.">
|
|
||||||
<EuiFlexGroup alignItems="center">
|
|
||||||
<EuiFlexItem style={{ overflow: 'auto' }}>
|
|
||||||
<EuiCodeBlock style={{ whiteSpace: 'nowrap' }} paddingSize="s">
|
|
||||||
{pdfUrl}
|
|
||||||
</EuiCodeBlock>
|
|
||||||
</EuiFlexItem>
|
|
||||||
|
|
||||||
<EuiFlexItem grow={false}>
|
{this.props.options}
|
||||||
<Clipboard
|
|
||||||
content={pdfUrl}
|
<EuiButton
|
||||||
onCopy={() => {
|
fill
|
||||||
this.props.onCopy('pdf');
|
onClick={() => {
|
||||||
closePopover();
|
closePopover();
|
||||||
}}
|
this.exportPdf();
|
||||||
>
|
}}
|
||||||
<EuiButtonIcon aria-label="Copy to clipboard" iconType="copy" />
|
size="s"
|
||||||
</Clipboard>
|
style={{ width: '100%' }}
|
||||||
</EuiFlexItem>
|
>
|
||||||
</EuiFlexGroup>
|
Generate PDF
|
||||||
</EuiFormRow>
|
</EuiButton>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
|
||||||
|
<EuiText size="s">
|
||||||
|
<p>
|
||||||
|
Alternatively, copy this POST URL to call generation from outside Kibana or from
|
||||||
|
Watcher.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
|
||||||
|
<Clipboard
|
||||||
|
content={pdfUrl}
|
||||||
|
onCopy={() => {
|
||||||
|
this.props.onCopy('pdf');
|
||||||
|
closePopover();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EuiButton
|
||||||
|
aria-label="Copy to clipboard"
|
||||||
|
iconType="copy"
|
||||||
|
size="s"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
Copy POST URL
|
||||||
|
</EuiButton>
|
||||||
|
</Clipboard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderPanelTree = closePopover => ({
|
||||||
|
id: 0,
|
||||||
|
title: 'Share this workpad',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'Download as JSON',
|
||||||
|
icon: <EuiIcon type="exportAction" size="m" />,
|
||||||
|
onClick: () => {
|
||||||
|
closePopover();
|
||||||
|
this.downloadWorkpad();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PDF Reports',
|
||||||
|
icon: 'document',
|
||||||
|
panel: {
|
||||||
|
id: 1,
|
||||||
|
title: 'PDF Reports',
|
||||||
|
content: this.props.enabled
|
||||||
|
? this.renderPDFControls(closePopover)
|
||||||
|
: this.renderDisabled(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
renderDisabled = () => {
|
renderDisabled = () => {
|
||||||
|
const reportingConfig = `xpack.reporting:
|
||||||
|
enabled: true
|
||||||
|
capture.browser.type: chromium`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="canvasWorkpadExport__panelContent">
|
||||||
Export to PDF is disabled. You must configure reporting to use the Chromium browser. Add
|
<EuiText size="s">
|
||||||
this to your kibana.yml file.
|
<p>
|
||||||
|
Export to PDF is disabled. You must configure reporting to use the Chromium browser. Add
|
||||||
|
this to your <EuiCode>kibana.yml</EuiCode> file.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<EuiCodeBlock paddingSize="s" language="yml">
|
<Clipboard content={reportingConfig} onCopy={() => this.props.onCopy('reportingConfig')}>
|
||||||
xpack.reporting.capture.browser.type: chromium
|
<EuiCodeBlock
|
||||||
</EuiCodeBlock>
|
className="canvasWorkpadExport__reportingConfig"
|
||||||
|
paddingSize="s"
|
||||||
|
fontSize="s"
|
||||||
|
language="yml"
|
||||||
|
>
|
||||||
|
{reportingConfig}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
</Clipboard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const exportControl = togglePopover => (
|
const exportControl = togglePopover => (
|
||||||
<EuiButtonIcon iconType="exportAction" aria-label="Create PDF" onClick={togglePopover} />
|
<EuiButtonIcon iconType="share" aria-label="Share this workpad" onClick={togglePopover} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: replace this with `showShareContextMenu` in `ui/share` once it's been converted to React
|
||||||
return (
|
return (
|
||||||
<Popover button={exportControl} tooltip="Export workpad" tooltipPosition="bottom">
|
<Popover
|
||||||
|
button={exportControl}
|
||||||
|
panelPaddingSize="none"
|
||||||
|
tooltip="Share workpad"
|
||||||
|
tooltipPosition="bottom"
|
||||||
|
>
|
||||||
{({ closePopover }) => (
|
{({ closePopover }) => (
|
||||||
<EuiFlexGroup justifyContent="flexEnd">
|
<EuiContextMenu
|
||||||
<EuiFlexItem grow={false} style={{ maxWidth: '300px' }}>
|
initialPanelId={0}
|
||||||
{this.props.enabled && this.renderControls(closePopover)}
|
panels={this.flattenPanelTree(this.renderPanelTree(closePopover))}
|
||||||
{!this.props.enabled && this.renderDisabled()}
|
/>
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
.canvasWorkpadExport__panelContent {
|
||||||
|
padding: $euiSize;
|
||||||
|
}
|
||||||
|
.canvasWorkpadExport__reportingConfig {
|
||||||
|
.euiCodeBlock__pre {
|
||||||
|
@include euiScrollBar;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { compose, withState, getContext, withHandlers } from 'recompose';
|
import { compose, withState, getContext, withHandlers } from 'recompose';
|
||||||
import fileSaver from 'file-saver';
|
|
||||||
import * as workpadService from '../../lib/workpad_service';
|
import * as workpadService from '../../lib/workpad_service';
|
||||||
import { notify } from '../../lib/notify';
|
import { notify } from '../../lib/notify';
|
||||||
import { canUserWrite } from '../../state/selectors/app';
|
import { canUserWrite } from '../../state/selectors/app';
|
||||||
import { getWorkpad } from '../../state/selectors/workpad';
|
import { getWorkpad } from '../../state/selectors/workpad';
|
||||||
import { getId } from '../../lib/get_id';
|
import { getId } from '../../lib/get_id';
|
||||||
import { setCanUserWrite } from '../../state/actions/transient';
|
import { setCanUserWrite } from '../../state/actions/transient';
|
||||||
|
import { downloadWorkpad } from '../../lib/download_workpad';
|
||||||
import { WorkpadLoader as Component } from './workpad_loader';
|
import { WorkpadLoader as Component } from './workpad_loader';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -46,7 +46,7 @@ export const WorkpadLoader = compose(
|
||||||
notify.error(err, { title: `Couldn't upload workpad` });
|
notify.error(err, { title: `Couldn't upload workpad` });
|
||||||
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
||||||
// https://github.com/elastic/kibana/issues/20277
|
// https://github.com/elastic/kibana/issues/20277
|
||||||
if (err.response.status === 403) {
|
if (err.response && err.response.status === 403) {
|
||||||
props.setCanUserWrite(false);
|
props.setCanUserWrite(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,15 +67,7 @@ export const WorkpadLoader = compose(
|
||||||
},
|
},
|
||||||
|
|
||||||
// Workpad import/export methods
|
// Workpad import/export methods
|
||||||
downloadWorkpad: () => async workpadId => {
|
downloadWorkpad: () => workpadId => downloadWorkpad(workpadId),
|
||||||
try {
|
|
||||||
const workpad = await workpadService.get(workpadId);
|
|
||||||
const jsonBlob = new Blob([JSON.stringify(workpad)], { type: 'application/json' });
|
|
||||||
fileSaver.saveAs(jsonBlob, `canvas-workpad-${workpad.name}-${workpad.id}.json`);
|
|
||||||
} catch (err) {
|
|
||||||
notify.error(err, { title: `Couldn't download workpad` });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Clone workpad given an id
|
// Clone workpad given an id
|
||||||
cloneWorkpad: props => async workpadId => {
|
cloneWorkpad: props => async workpadId => {
|
||||||
|
@ -89,7 +81,7 @@ export const WorkpadLoader = compose(
|
||||||
notify.error(err, { title: `Couldn't clone workpad` });
|
notify.error(err, { title: `Couldn't clone workpad` });
|
||||||
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
||||||
// https://github.com/elastic/kibana/issues/20277
|
// https://github.com/elastic/kibana/issues/20277
|
||||||
if (err.response.status === 403) {
|
if (err.response && err.response.status === 403) {
|
||||||
props.setCanUserWrite(false);
|
props.setCanUserWrite(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +114,7 @@ export const WorkpadLoader = compose(
|
||||||
errors.push(result.id);
|
errors.push(result.id);
|
||||||
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
// TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced
|
||||||
// https://github.com/elastic/kibana/issues/20277
|
// https://github.com/elastic/kibana/issues/20277
|
||||||
if (result.err.response.status === 403) {
|
if (result.err.response && result.err.response.status === 403) {
|
||||||
props.setCanUserWrite(false);
|
props.setCanUserWrite(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -142,7 +142,7 @@ export class WorkpadLoader extends React.PureComponent {
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiToolTip content="Download">
|
<EuiToolTip content="Download">
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
iconType="sortDown"
|
iconType="exportAction"
|
||||||
onClick={() => this.props.downloadWorkpad(workpad.id)}
|
onClick={() => this.props.downloadWorkpad(workpad.id)}
|
||||||
aria-label="Download Workpad"
|
aria-label="Download Workpad"
|
||||||
/>
|
/>
|
||||||
|
@ -288,7 +288,7 @@ export class WorkpadLoader extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
const downloadButton = (
|
const downloadButton = (
|
||||||
<EuiButton color="secondary" onClick={this.downloadWorkpads} iconType="sortDown">
|
<EuiButton color="secondary" onClick={this.downloadWorkpads} iconType="exportAction">
|
||||||
{`Download (${selectedWorkpads.length})`}
|
{`Download (${selectedWorkpads.length})`}
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
);
|
);
|
||||||
|
|
18
x-pack/plugins/canvas/public/lib/download_workpad.js
Normal file
18
x-pack/plugins/canvas/public/lib/download_workpad.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
import fileSaver from 'file-saver';
|
||||||
|
import { notify } from './notify';
|
||||||
|
import * as workpadService from './workpad_service';
|
||||||
|
|
||||||
|
export const downloadWorkpad = async workpadId => {
|
||||||
|
try {
|
||||||
|
const workpad = await workpadService.get(workpadId);
|
||||||
|
const jsonBlob = new Blob([JSON.stringify(workpad)], { type: 'application/json' });
|
||||||
|
fileSaver.saveAs(jsonBlob, `canvas-workpad-${workpad.name}-${workpad.id}.json`);
|
||||||
|
} catch (err) {
|
||||||
|
notify.error(err, { title: `Couldn't download workpad` });
|
||||||
|
}
|
||||||
|
};
|
|
@ -55,26 +55,26 @@ export const esPersistMiddleware = ({ getState }) => {
|
||||||
if (workpadChanged(curState, newState) || assetsChanged(curState, newState)) {
|
if (workpadChanged(curState, newState) || assetsChanged(curState, newState)) {
|
||||||
const persistedWorkpad = getWorkpadPersisted(getState());
|
const persistedWorkpad = getWorkpadPersisted(getState());
|
||||||
return update(persistedWorkpad.id, persistedWorkpad).catch(err => {
|
return update(persistedWorkpad.id, persistedWorkpad).catch(err => {
|
||||||
if (err.response.status === 400) {
|
const statusCode = err.response && err.response.status;
|
||||||
return notify.error(err.response, {
|
switch (statusCode) {
|
||||||
title: `Couldn't save your changes to Elasticsearch`,
|
case 400:
|
||||||
});
|
return notify.error(err.response, {
|
||||||
}
|
|
||||||
|
|
||||||
if (err.response.status === 413) {
|
|
||||||
return notify.error(
|
|
||||||
`The server gave a response that the workpad data was too large. This
|
|
||||||
usually means uploaded image assets that are too large for Kibana or
|
|
||||||
a proxy. Try removing some assets in the asset manager.`,
|
|
||||||
{
|
|
||||||
title: `Couldn't save your changes to Elasticsearch`,
|
title: `Couldn't save your changes to Elasticsearch`,
|
||||||
}
|
});
|
||||||
);
|
case 413:
|
||||||
|
return notify.error(
|
||||||
|
`The server gave a response that the workpad data was too large. This
|
||||||
|
usually means uploaded image assets that are too large for Kibana or
|
||||||
|
a proxy. Try removing some assets in the asset manager.`,
|
||||||
|
{
|
||||||
|
title: `Couldn't save your changes to Elasticsearch`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return notify.error(err, {
|
||||||
|
title: `Couldn't update workpad`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return notify.error(err.response, {
|
|
||||||
title: `Couldn't update workpad`,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
@import '../components/autocomplete/autocomplete';
|
@import '../components/autocomplete/autocomplete';
|
||||||
@import '../components/border_connection/border_connection';
|
@import '../components/border_connection/border_connection';
|
||||||
@import '../components/border_resize_handle/border_resize_handle';
|
@import '../components/border_resize_handle/border_resize_handle';
|
||||||
|
@import '../components/clipboard/clipboard';
|
||||||
@import '../components/color_dot/color_dot';
|
@import '../components/color_dot/color_dot';
|
||||||
@import '../components/color_palette/color_palette';
|
@import '../components/color_palette/color_palette';
|
||||||
@import '../components/color_picker_mini/color_picker_mini';
|
@import '../components/color_picker_mini/color_picker_mini';
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
@import '../components/toolbar/toolbar';
|
@import '../components/toolbar/toolbar';
|
||||||
@import '../components/toolbar/tray/tray';
|
@import '../components/toolbar/tray/tray';
|
||||||
@import '../components/workpad/workpad';
|
@import '../components/workpad/workpad';
|
||||||
|
@import '../components/workpad_export/workpad_export';
|
||||||
@import '../components/workpad_loader/workpad_loader';
|
@import '../components/workpad_loader/workpad_loader';
|
||||||
@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
|
@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
|
||||||
@import '../components/workpad_page/workpad_page';
|
@import '../components/workpad_page/workpad_page';
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function workpad(server) {
|
||||||
const savedObjectsClient = req.getSavedObjectsClient();
|
const savedObjectsClient = req.getSavedObjectsClient();
|
||||||
|
|
||||||
if (!req.payload) {
|
if (!req.payload) {
|
||||||
return Promise.resolve(boom.badRequest('A workpad payload is required'));
|
return Promise.reject(boom.badRequest('A workpad payload is required'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { JoinEditor } from './view';
|
import { JoinEditor } from './view';
|
||||||
|
import { getSelectedLayer, getSelectedLayerJoinDescriptors } from '../../../selectors/map_selectors';
|
||||||
import { setJoinsForLayer } from '../../../actions/store_actions';
|
import { setJoinsForLayer } from '../../../actions/store_actions';
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
|
@ -16,12 +17,10 @@ function mapDispatchToProps(dispatch) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps({}, props) {
|
function mapStateToProps(state = {}) {
|
||||||
return {
|
return {
|
||||||
joins: props.layer.getJoins().map(join => {
|
joins: getSelectedLayerJoinDescriptors(state),
|
||||||
return join.toDescriptor();
|
layer: getSelectedLayer(state),
|
||||||
}),
|
|
||||||
layer: props.layer,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class LayerPanel extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPanel>
|
<EuiPanel>
|
||||||
<JoinEditor layer={this.props.selectedLayer}/>
|
<JoinEditor/>
|
||||||
</EuiPanel>
|
</EuiPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,15 +135,6 @@ export const getDataFilters = createSelector(
|
||||||
|
|
||||||
export const getDataSources = createSelector(getMetadata, metadata => metadata ? metadata.data_sources : null);
|
export const getDataSources = createSelector(getMetadata, metadata => metadata ? metadata.data_sources : null);
|
||||||
|
|
||||||
export const getSelectedLayer = createSelector(
|
|
||||||
getSelectedLayerId,
|
|
||||||
getLayerListRaw,
|
|
||||||
getDataSources,
|
|
||||||
(selectedLayerId, layerList, dataSources) => {
|
|
||||||
const selectedLayer = layerList.find(layerDescriptor => layerDescriptor.id === selectedLayerId);
|
|
||||||
return createLayerInstance(selectedLayer, dataSources);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getLayerList = createSelector(
|
export const getLayerList = createSelector(
|
||||||
getLayerListRaw,
|
getLayerListRaw,
|
||||||
getDataSources,
|
getDataSources,
|
||||||
|
@ -152,4 +143,19 @@ export const getLayerList = createSelector(
|
||||||
createLayerInstance(layerDescriptor, dataSources));
|
createLayerInstance(layerDescriptor, dataSources));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getSelectedLayer = createSelector(
|
||||||
|
getSelectedLayerId,
|
||||||
|
getLayerList,
|
||||||
|
(selectedLayerId, layerList) => {
|
||||||
|
return layerList.find(layer => layer.getId() === selectedLayerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getSelectedLayerJoinDescriptors = createSelector(
|
||||||
|
getSelectedLayer,
|
||||||
|
(selectedLayer) => {
|
||||||
|
return selectedLayer.getJoins().map(join => {
|
||||||
|
return join.toDescriptor();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export const getTemporaryLayers = createSelector(getLayerList, (layerList) => layerList.filter(layer => layer.isTemporary()));
|
export const getTemporaryLayers = createSelector(getLayerList, (layerList) => layerList.filter(layer => layer.isTemporary()));
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const ALL_SOURCES = [
|
||||||
EMSFileSource,
|
EMSFileSource,
|
||||||
EMSTMSSource,
|
EMSTMSSource,
|
||||||
KibanaRegionmapSource,
|
KibanaRegionmapSource,
|
||||||
|
KibanaTilemapSource,
|
||||||
XYZTMSSource,
|
XYZTMSSource,
|
||||||
WMSSource,
|
WMSSource,
|
||||||
KibanaTilemapSource
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { emsServiceSettings } from '../../../kibana_services';
|
||||||
export class EMSFileSource extends VectorSource {
|
export class EMSFileSource extends VectorSource {
|
||||||
|
|
||||||
static type = 'EMS_FILE';
|
static type = 'EMS_FILE';
|
||||||
static typeDisplayName = 'Elastic Maps Service region boundaries';
|
static typeDisplayName = 'Elastic Maps Service vector shapes';
|
||||||
|
|
||||||
static createDescriptor(id) {
|
static createDescriptor(id) {
|
||||||
return {
|
return {
|
||||||
|
@ -60,7 +60,9 @@ export class EMSFileSource extends VectorSource {
|
||||||
<strong>{EMSFileSource.typeDisplayName}</strong>
|
<strong>{EMSFileSource.typeDisplayName}</strong>
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">Political boundry vectors hosted by EMS.</p>
|
<p className="euiTextColor--subdued">
|
||||||
|
Vector shapes of administrative boundaries from Elastic Maps Service
|
||||||
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -108,10 +110,6 @@ export class EMSFileSource extends VectorSource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async isTimeAware() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
canFormatFeatureProperties() {
|
canFormatFeatureProperties() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import _ from 'lodash';
|
||||||
export class EMSTMSSource extends TMSSource {
|
export class EMSTMSSource extends TMSSource {
|
||||||
|
|
||||||
static type = 'EMS_TMS';
|
static type = 'EMS_TMS';
|
||||||
static typeDisplayName = 'Elastic Maps Service Tile Service';
|
static typeDisplayName = 'Elastic Maps Service tiles';
|
||||||
|
|
||||||
static createDescriptor(serviceId) {
|
static createDescriptor(serviceId) {
|
||||||
return {
|
return {
|
||||||
|
@ -58,7 +58,9 @@ export class EMSTMSSource extends TMSSource {
|
||||||
<strong>{EMSTMSSource.typeDisplayName}</strong>
|
<strong>{EMSTMSSource.typeDisplayName}</strong>
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">Tile services hosted by EMS.</p>
|
<p className="euiTextColor--subdued">
|
||||||
|
Map tiles from Elastic Maps Service
|
||||||
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -91,8 +91,7 @@ export class ESGeohashGridSource extends VectorSource {
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">
|
<p className="euiTextColor--subdued">
|
||||||
Group documents into grid cells and display metrics for each cell.
|
Group geospatial data in grids with metrics for each gridded cell
|
||||||
Great for displaying large datasets.
|
|
||||||
</p>
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export class ESSearchSource extends VectorSource {
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">
|
<p className="euiTextColor--subdued">
|
||||||
Display documents from an elasticsearch index.
|
Geospatial data from an Elasticsearch index
|
||||||
</p>
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>);
|
</Fragment>);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
export class KibanaRegionmapSource extends VectorSource {
|
export class KibanaRegionmapSource extends VectorSource {
|
||||||
|
|
||||||
static type = 'REGIONMAP_FILE';
|
static type = 'REGIONMAP_FILE';
|
||||||
static typeDisplayName = 'Custom region boundaries';
|
static typeDisplayName = 'Custom vector shapes';
|
||||||
|
|
||||||
constructor(descriptor, { ymlFileLayers }) {
|
constructor(descriptor, { ymlFileLayers }) {
|
||||||
super(descriptor);
|
super(descriptor);
|
||||||
|
@ -62,7 +62,7 @@ export class KibanaRegionmapSource extends VectorSource {
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">
|
<p className="euiTextColor--subdued">
|
||||||
Region map boundary layers configured in your config/kibana.yml file.
|
Vector shapes from static files configured in kibana.yml.
|
||||||
</p>
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
export class KibanaTilemapSource extends TMSSource {
|
export class KibanaTilemapSource extends TMSSource {
|
||||||
|
|
||||||
static type = 'KIBANA_TILEMAP';
|
static type = 'KIBANA_TILEMAP';
|
||||||
static typeDisplayName = 'TMS from kibana.yml';
|
static typeDisplayName = 'Custom Tile Map Service';
|
||||||
|
|
||||||
static createDescriptor(url) {
|
static createDescriptor(url) {
|
||||||
return {
|
return {
|
||||||
|
@ -41,7 +41,9 @@ export class KibanaTilemapSource extends TMSSource {
|
||||||
<strong>{KibanaTilemapSource.typeDisplayName}</strong>
|
<strong>{KibanaTilemapSource.typeDisplayName}</strong>
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">TMS service configured in kibana.yml</p>
|
<p className="euiTextColor--subdued">
|
||||||
|
Map tiles configured in kibana.yml
|
||||||
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,17 +57,14 @@ export class VectorSource extends ASource {
|
||||||
}
|
}
|
||||||
|
|
||||||
isFilterByMapBounds() {
|
isFilterByMapBounds() {
|
||||||
console.warn('Should implement VectorSource#isFilterByMapBounds');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNumberFields() {
|
async getNumberFields() {
|
||||||
console.warn('Should implement VectorSource#getNumberFields');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStringFields() {
|
async getStringFields() {
|
||||||
console.warn('Should implement VectorSource@getStringFields');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +90,7 @@ export class VectorSource extends ASource {
|
||||||
}
|
}
|
||||||
|
|
||||||
async isTimeAware() {
|
async isTimeAware() {
|
||||||
throw new Error('Should implement');
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class WMSSource extends TMSSource {
|
||||||
|
|
||||||
static type = 'WMS';
|
static type = 'WMS';
|
||||||
|
|
||||||
static typeDisplayName = 'WMS';
|
static typeDisplayName = 'Web Map Service';
|
||||||
|
|
||||||
static createDescriptor({ serviceUrl, layers, styles }) {
|
static createDescriptor({ serviceUrl, layers, styles }) {
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +46,9 @@ export class WMSSource extends TMSSource {
|
||||||
<strong>{WMSSource.typeDisplayName}</strong>
|
<strong>{WMSSource.typeDisplayName}</strong>
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">Web Map Service (WMS)</p>
|
<p className="euiTextColor--subdued">
|
||||||
|
Maps from OGC Standard WMS
|
||||||
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class XYZTMSSource extends TMSSource {
|
||||||
|
|
||||||
static type = 'EMS_XYZ';
|
static type = 'EMS_XYZ';
|
||||||
|
|
||||||
static typeDisplayName = 'TMS XYZ';
|
static typeDisplayName = 'Tile Map Service from URL';
|
||||||
|
|
||||||
static createDescriptor(urlTemplate) {
|
static createDescriptor(urlTemplate) {
|
||||||
return {
|
return {
|
||||||
|
@ -44,7 +44,9 @@ export class XYZTMSSource extends TMSSource {
|
||||||
<strong>{XYZTMSSource.typeDisplayName}</strong>
|
<strong>{XYZTMSSource.typeDisplayName}</strong>
|
||||||
<EuiSpacer size="xs" />
|
<EuiSpacer size="xs" />
|
||||||
<EuiText size="s" color="subdued">
|
<EuiText size="s" color="subdued">
|
||||||
<p className="euiTextColor--subdued">Tile Map Service with XYZ url.</p>
|
<p className="euiTextColor--subdued">
|
||||||
|
Map tiles from a URL that includes the XYZ coordinates
|
||||||
|
</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -223,7 +223,7 @@ export class VectorLayer extends ALayer {
|
||||||
join: join
|
join: join
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
startLoading(sourceDataId, requestToken, { timeFilters: dataFilters.timeFilters });
|
startLoading(sourceDataId, requestToken, dataFilters);
|
||||||
const leftSourceName = await this.getSourceName();
|
const leftSourceName = await this.getSourceName();
|
||||||
const {
|
const {
|
||||||
rawData,
|
rawData,
|
||||||
|
|
|
@ -17,5 +17,5 @@ export const enrichResponse = async (response, callWithRequest) => {
|
||||||
// silently swallow enricher response errors
|
// silently swallow enricher response errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response;
|
return enrichedResponse;
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,6 +75,10 @@
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mlAnomalyCategoryExamples__header {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.mlAnomalyCategoryExamples__link {
|
.mlAnomalyCategoryExamples__link {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,11 @@ import { AnomalyDetails } from './anomaly_details';
|
||||||
|
|
||||||
import { mlTableService } from '../../services/table_service';
|
import { mlTableService } from '../../services/table_service';
|
||||||
import { RuleEditorFlyout } from '../../components/rule_editor';
|
import { RuleEditorFlyout } from '../../components/rule_editor';
|
||||||
|
import { ml } from '../../services/ml_api_service';
|
||||||
import {
|
import {
|
||||||
INFLUENCERS_LIMIT,
|
INFLUENCERS_LIMIT,
|
||||||
ANOMALIES_TABLE_TABS
|
ANOMALIES_TABLE_TABS,
|
||||||
|
MAX_CHARS
|
||||||
} from './anomalies_table_constants';
|
} from './anomalies_table_constants';
|
||||||
|
|
||||||
class AnomaliesTable extends Component {
|
class AnomaliesTable extends Component {
|
||||||
|
@ -71,18 +73,36 @@ class AnomaliesTable extends Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleRow = (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => {
|
toggleRow = async (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => {
|
||||||
const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
|
const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
|
||||||
if (itemIdToExpandedRowMap[item.rowId]) {
|
if (itemIdToExpandedRowMap[item.rowId]) {
|
||||||
delete itemIdToExpandedRowMap[item.rowId];
|
delete itemIdToExpandedRowMap[item.rowId];
|
||||||
} else {
|
} else {
|
||||||
const examples = (item.entityName === 'mlcategory') ?
|
const examples = (item.entityName === 'mlcategory') ?
|
||||||
_.get(this.props.tableData, ['examplesByJobId', item.jobId, item.entityValue]) : undefined;
|
_.get(this.props.tableData, ['examplesByJobId', item.jobId, item.entityValue]) : undefined;
|
||||||
|
let definition = undefined;
|
||||||
|
|
||||||
|
if (examples !== undefined) {
|
||||||
|
try {
|
||||||
|
definition = await ml.results.getCategoryDefinition(item.jobId, item.source.mlcategory[0]);
|
||||||
|
|
||||||
|
if (definition.terms && definition.terms.length > MAX_CHARS) {
|
||||||
|
definition.terms = `${definition.terms.substring(0, MAX_CHARS)}...`;
|
||||||
|
}
|
||||||
|
if (definition.regex && definition.regex.length > MAX_CHARS) {
|
||||||
|
definition.terms = `${definition.regex.substring(0, MAX_CHARS)}...`;
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
console.log('Error fetching category definition for row item.', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
itemIdToExpandedRowMap[item.rowId] = (
|
itemIdToExpandedRowMap[item.rowId] = (
|
||||||
<AnomalyDetails
|
<AnomalyDetails
|
||||||
tabIndex={tab}
|
tabIndex={tab}
|
||||||
anomaly={item}
|
anomaly={item}
|
||||||
examples={examples}
|
examples={examples}
|
||||||
|
definition={definition}
|
||||||
isAggregatedData={this.isShowingAggregatedData()}
|
isAggregatedData={this.isShowingAggregatedData()}
|
||||||
filter={this.props.filter}
|
filter={this.props.filter}
|
||||||
influencersLimit={INFLUENCERS_LIMIT}
|
influencersLimit={INFLUENCERS_LIMIT}
|
||||||
|
|
|
@ -13,3 +13,5 @@ export const ANOMALIES_TABLE_TABS = {
|
||||||
DETAILS: 0,
|
DETAILS: 0,
|
||||||
CATEGORY_EXAMPLES: 1
|
CATEGORY_EXAMPLES: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MAX_CHARS = 500;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiIcon,
|
EuiIcon,
|
||||||
|
EuiIconTip,
|
||||||
EuiLink,
|
EuiLink,
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiTabbedContent,
|
EuiTabbedContent,
|
||||||
|
@ -35,6 +36,7 @@ import {
|
||||||
} from '../../../common/util/anomaly_utils';
|
} from '../../../common/util/anomaly_utils';
|
||||||
import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact';
|
import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact';
|
||||||
import { formatValue } from '../../formatters/format_value';
|
import { formatValue } from '../../formatters/format_value';
|
||||||
|
import { MAX_CHARS } from './anomalies_table_constants';
|
||||||
|
|
||||||
const TIME_FIELD_NAME = 'timestamp';
|
const TIME_FIELD_NAME = 'timestamp';
|
||||||
|
|
||||||
|
@ -218,6 +220,8 @@ export class AnomalyDetails extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategoryExamples() {
|
renderCategoryExamples() {
|
||||||
|
const { examples, definition } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
direction="column"
|
direction="column"
|
||||||
|
@ -225,9 +229,54 @@ export class AnomalyDetails extends Component {
|
||||||
gutterSize="m"
|
gutterSize="m"
|
||||||
className="mlAnomalyCategoryExamples"
|
className="mlAnomalyCategoryExamples"
|
||||||
>
|
>
|
||||||
{this.props.examples.map((example, i) => {
|
{(definition !== undefined && definition.terms) &&
|
||||||
|
<Fragment>
|
||||||
|
<EuiFlexItem key={`example-terms`}>
|
||||||
|
<EuiText size="xs">
|
||||||
|
<h4 className="mlAnomalyCategoryExamples__header">Terms</h4>
|
||||||
|
<EuiIconTip
|
||||||
|
aria-label="Description"
|
||||||
|
type="questionInCircle"
|
||||||
|
color="subdued"
|
||||||
|
size="s"
|
||||||
|
content={`A space separated list of the common tokens that are matched in values of the category
|
||||||
|
(may have been truncated to a max character limit of ${MAX_CHARS})`}
|
||||||
|
/>
|
||||||
|
</EuiText>
|
||||||
|
<EuiText size="xs">
|
||||||
|
{definition.terms}
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</Fragment> }
|
||||||
|
{(definition !== undefined && definition.regex) &&
|
||||||
|
<Fragment>
|
||||||
|
<EuiFlexItem key={`example-regex`}>
|
||||||
|
<EuiText size="xs">
|
||||||
|
<h4 className="mlAnomalyCategoryExamples__header">Regex</h4>
|
||||||
|
<EuiIconTip
|
||||||
|
aria-label="Description"
|
||||||
|
type="questionInCircle"
|
||||||
|
color="subdued"
|
||||||
|
size="s"
|
||||||
|
content={`The regular expression that is used to search for values that match the category
|
||||||
|
(may have been truncated to a max character limit of ${MAX_CHARS})`}
|
||||||
|
/>
|
||||||
|
</EuiText>
|
||||||
|
<EuiText size="xs">
|
||||||
|
{definition.regex}
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiSpacer size="l" />
|
||||||
|
</Fragment>}
|
||||||
|
|
||||||
|
{examples.map((example, i) => {
|
||||||
return (
|
return (
|
||||||
<EuiFlexItem key={`example${i}`}>
|
<EuiFlexItem key={`example${i}`}>
|
||||||
|
{(i === 0 && definition !== undefined) &&
|
||||||
|
<EuiText size="s">
|
||||||
|
<h4>Examples</h4>
|
||||||
|
</EuiText>}
|
||||||
<span className="mlAnomalyCategoryExamples__item">{example}</span>
|
<span className="mlAnomalyCategoryExamples__item">{example}</span>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
);
|
);
|
||||||
|
@ -384,6 +433,7 @@ export class AnomalyDetails extends Component {
|
||||||
AnomalyDetails.propTypes = {
|
AnomalyDetails.propTypes = {
|
||||||
anomaly: PropTypes.object.isRequired,
|
anomaly: PropTypes.object.isRequired,
|
||||||
examples: PropTypes.array,
|
examples: PropTypes.array,
|
||||||
|
definition: PropTypes.object,
|
||||||
isAggregatedData: PropTypes.bool,
|
isAggregatedData: PropTypes.bool,
|
||||||
filter: PropTypes.func,
|
filter: PropTypes.func,
|
||||||
influencersLimit: PropTypes.number,
|
influencersLimit: PropTypes.number,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow, mount } from 'enzyme';
|
||||||
import { AnomalyDetails } from './anomaly_details';
|
import { AnomalyDetails } from './anomaly_details';
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
@ -86,4 +86,75 @@ describe('AnomalyDetails', () => {
|
||||||
);
|
);
|
||||||
expect(wrapper.prop('initialSelectedTab').id).toBe('Category examples');
|
expect(wrapper.prop('initialSelectedTab').id).toBe('Category examples');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Renders with terms and regex when definition prop is not undefined', () => {
|
||||||
|
const categoryTabProps = {
|
||||||
|
...props,
|
||||||
|
tabIndex: 1,
|
||||||
|
definition: {
|
||||||
|
terms: 'example terms for test',
|
||||||
|
regex: '.*?DBMS.+?ERROR.+?svc_prod.+?Err.+?Microsoft.+?ODBC.+?SQL.+?Server.+?Driver'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<AnomalyDetails {...categoryTabProps} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(true);
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(true);
|
||||||
|
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renders only with examples when definition prop is undefined', () => {
|
||||||
|
const categoryTabProps = {
|
||||||
|
...props,
|
||||||
|
tabIndex: 1,
|
||||||
|
definition: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<AnomalyDetails {...categoryTabProps} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(false);
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(false);
|
||||||
|
expect(wrapper.contains(<h4>Examples</h4>)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renders only with terms when definition.regex is undefined', () => {
|
||||||
|
const categoryTabProps = {
|
||||||
|
...props,
|
||||||
|
tabIndex: 1,
|
||||||
|
definition: {
|
||||||
|
terms: 'example terms for test',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<AnomalyDetails {...categoryTabProps} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(false);
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(true);
|
||||||
|
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renders only with regex when definition.terms is undefined', () => {
|
||||||
|
const categoryTabProps = {
|
||||||
|
...props,
|
||||||
|
tabIndex: 1,
|
||||||
|
definition: {
|
||||||
|
regex: '.*?DBMS.+?ERROR.+?svc_prod.+?Err.+?Microsoft.+?ODBC.+?SQL.+?Server.+?Driver'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<AnomalyDetails {...categoryTabProps} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(true);
|
||||||
|
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(false);
|
||||||
|
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue