diff --git a/.eslintignore b/.eslintignore index 61128f38d867..aed4548f41f0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,7 +5,7 @@ bower_components /.es /plugins /optimize -/dlls +/built_assets /src/fixtures/vislib/mock_data /src/ui/public/angular-bootstrap /src/ui/public/flot-charts diff --git a/.gitignore b/.gitignore index 167d677100df..0ed1cf89ba58 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ node_modules !/src/dev/notice/__fixtures__/node_modules trash /optimize -/dlls +/built_assets target /build .jruby @@ -44,4 +44,3 @@ package-lock.json *.sublime-* npm-debug.log* .tern-project -**/public/index.css diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index ca2050e574ff..20d57d1d2ae0 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -39,6 +39,7 @@ document. `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 working on big documents. Set this property to `false` to disable highlighting. +`doc_table:hideTimeColumn`:: Hide the 'Time' column in Discover and in all Saved Searches on Dashboards. `search:includeFrozen`:: Will include {ref}/frozen-indices.html[frozen indices] in results if enabled. Searching through frozen indices might increase the search time. `courier:maxSegmentCount`:: Kibana splits requests in the Discover app into segments to limit the size of requests sent to diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index 2926ddd89520..6bfd36bc1c06 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -94,13 +94,13 @@ https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless === Creating 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. -. Go to the pattern's *Scripted Fields* tab. -. Click *Add Scripted Field*. +. Go to the pattern's *Scripted fields* tab. +. Click *Add 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. -. Click *Save Scripted Field*. +. Click *Create field*. For more information about scripted fields in Elasticsearch, see {ref}/modules-scripting.html[Scripting]. @@ -110,9 +110,10 @@ For more information about scripted fields in Elasticsearch, see === Updating 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. -. 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 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 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. -. Confirm that you really want to delete the field. +. Click *Delete* in the confirmation window. diff --git a/package.json b/package.json index 8845d5d16852..0823226c367b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "extraPatterns": [ "build", "optimize", - "dlls", + "built_assets", ".eslintcache" ] } diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js index 31028154bf8c..be8bebbac280 100644 --- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js +++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js @@ -64,7 +64,7 @@ export const CleanClientModulesOnDLLTask = { ]; // 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 // from any whitelisted module diff --git a/src/dev/build/tasks/transpile_scss_task.js b/src/dev/build/tasks/transpile_scss_task.js index 0c06a421bf50..c2004f1d9b21 100644 --- a/src/dev/build/tasks/transpile_scss_task.js +++ b/src/dev/build/tasks/transpile_scss_task.js @@ -34,7 +34,7 @@ export const TranspileScssTask = { const uiExports = collectUiExports(enabledPlugins); 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}`)); } catch (error) { const { message, line, file } = error; diff --git a/src/dev/notice/generate_notice_from_source.js b/src/dev/notice/generate_notice_from_source.js index 86ebd59553e2..31c40e7714ee 100644 --- a/src/dev/notice/generate_notice_from_source.js +++ b/src/dev/notice/generate_notice_from_source.js @@ -42,7 +42,7 @@ export async function generateNoticeFromSource({ productName, directory, log }) cwd: directory, nodir: true, ignore: [ - '{node_modules,build,target,dist,optimize,dlls}/**', + '{node_modules,build,target,dist,optimize,built_assets}/**', 'packages/*/{node_modules,build,target,dist}/**', 'x-pack/{node_modules,build,target,dist,optimize}/**', 'x-pack/packages/*/{node_modules,build,target,dist}/**', diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index 1965870d5ef1..0fb7042ac1cd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -380,7 +380,8 @@ function discoverController( } 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 { searchFields: fields, selectFields: fields diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js index 4e2dcd5a19a0..9d941d9b4234 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js @@ -158,11 +158,12 @@ export function onPremInstructions(apmIndexPattern) { index: apmIndexPattern, query: { bool: { - filter: { - exists: { - field: 'processor.name', - }, - }, + should: [ + { term: { 'processor.name': 'error' } }, + { term: { 'processor.name': 'transaction' } }, + { term: { 'processor.name': 'metric' } }, + { term: { 'processor.name': 'sourcemap' } }, + ], }, }, }, diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index 8c222aca15fb..ee646aacec19 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -269,6 +269,16 @@ export function getUiSettingDefaults() { }), 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': { name: i18n.translate('kbn.advancedSettings.courier.maxSegmentCountTitle', { defaultMessage: 'Maximum segment count', diff --git a/src/legacy/core_plugins/tests_bundle/index.js b/src/legacy/core_plugins/tests_bundle/index.js index 71e79f0ea7d1..146c8373a13f 100644 --- a/src/legacy/core_plugins/tests_bundle/index.js +++ b/src/legacy/core_plugins/tests_bundle/index.js @@ -70,7 +70,7 @@ export default (kibana) => { } testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`); - testGlobs.push(`${plugin.publicDir}/**/*.css`); + testGlobs.push(`built_assets/css/plugins/${plugin.id}/**/*.css`); }); } else { // add the modules from all of the apps @@ -80,7 +80,7 @@ export default (kibana) => { for (const plugin of plugins) { testGlobs.push(`${plugin.publicDir}/**/__tests__/**/*.js`); - testGlobs.push(`${plugin.publicDir}/**/*.css`); + testGlobs.push(`built_assets/css/plugins/${plugin.id}/**/*.css`); } } diff --git a/src/legacy/core_plugins/vega/public/data_model/vega_parser.js b/src/legacy/core_plugins/vega/public/data_model/vega_parser.js index 8f5d4fc6d3c4..ad76d424459f 100644 --- a/src/legacy/core_plugins/vega/public/data_model/vega_parser.js +++ b/src/legacy/core_plugins/vega/public/data_model/vega_parser.js @@ -185,7 +185,7 @@ export class VegaParser { delete this.spec.width; delete this.spec.height; } 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}', values: { autosizeParam: 'autosize=fit', diff --git a/src/legacy/core_plugins/vega/public/vega_type.js b/src/legacy/core_plugins/vega/public/vega_type.js index 28f5bd5bd298..f19d5db66e56 100644 --- a/src/legacy/core_plugins/vega/public/vega_type.js +++ b/src/legacy/core_plugins/vega/public/vega_type.js @@ -42,7 +42,7 @@ VisTypesRegistryProvider.register((Private) => { return VisFactory.createBaseVisualization({ name: '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', description: 'Vega and Vega-Lite are product names and should not be translated', }), diff --git a/src/optimize/bundles_route/bundles_route.js b/src/optimize/bundles_route/bundles_route.js index d00f4dd3378a..2be8de45e336 100644 --- a/src/optimize/bundles_route/bundles_route.js +++ b/src/optimize/bundles_route/bundles_route.js @@ -59,7 +59,7 @@ export function createBundlesRoute({ regularBundlesPath, dllBundlesPath, basePub return [ buildRouteForBundles(basePublicPath, '/bundles/', regularBundlesPath, fileHashCache), - buildRouteForBundles(basePublicPath, '/dlls/', dllBundlesPath, fileHashCache), + buildRouteForBundles(basePublicPath, '/built_assets/dlls/', dllBundlesPath, fileHashCache), ]; } diff --git a/src/optimize/bundles_route/proxy_bundles_route.js b/src/optimize/bundles_route/proxy_bundles_route.js index 300c6296a8d9..a15330ed5002 100644 --- a/src/optimize/bundles_route/proxy_bundles_route.js +++ b/src/optimize/bundles_route/proxy_bundles_route.js @@ -20,7 +20,7 @@ export function createProxyBundlesRoute({ host, port }) { return [ buildProxyRouteForBundles('/bundles/', host, port), - buildProxyRouteForBundles('/dlls/', host, port) + buildProxyRouteForBundles('/built_assets/dlls/', host, port) ]; } diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index eb0325591460..26065c90d345 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -46,7 +46,7 @@ export class DllCompiler { dllExt: '.bundle.dll.js', manifestExt: '.manifest.dll.json', styleExt: '.style.dll.css', - outputPath: fromRoot('./dlls'), + outputPath: fromRoot('built_assets/dlls'), publicPath: PUBLIC_PATH_PLACEHOLDER }; } diff --git a/src/optimize/index.js b/src/optimize/index.js index f8373eb4e6ae..b6ba806573e5 100644 --- a/src/optimize/index.js +++ b/src/optimize/index.js @@ -29,7 +29,7 @@ export default async (kbnServer, server, config) => { // bundles in a "middleware" style. // // 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 // to prevent complete rebuilds of the optimize content. const watch = config.get('optimize.watch'); diff --git a/src/optimize/watch/watch.js b/src/optimize/watch/watch.js index 7089524b8a46..6d46fd259b7c 100644 --- a/src/optimize/watch/watch.js +++ b/src/optimize/watch/watch.js @@ -33,7 +33,7 @@ export default async kbnServer => { * while the optimizer is running * * 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 */ diff --git a/src/server/sass/__fixtures__/index.scss b/src/server/sass/__fixtures__/index.scss new file mode 100644 index 000000000000..c542dd82ca68 --- /dev/null +++ b/src/server/sass/__fixtures__/index.scss @@ -0,0 +1,5 @@ +foo { + bar { + display: flex; + } +} diff --git a/src/server/sass/build.js b/src/server/sass/build.js index 55e4d26a287f..fcdb813b4e60 100644 --- a/src/server/sass/build.js +++ b/src/server/sass/build.js @@ -23,30 +23,24 @@ import fs from 'fs'; import sass from 'node-sass'; import autoprefixer from 'autoprefixer'; import postcss from 'postcss'; +import mkdirp from 'mkdirp'; const renderSass = promisify(sass.render); const writeFile = promisify(fs.writeFile); +const mkdirpAsync = promisify(mkdirp); export class Build { - constructor(source, log) { + constructor(source, log, targetPath) { this.source = source; this.log = log; + this.targetPath = targetPath; 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 */ - getGlob() { - return path.join(path.dirname(this.source), '**', '*.s{a,c}ss'); - } - async buildIfIncluded(path) { if (this.includedFiles && this.includedFiles.includes(path)) { await this.build(); @@ -61,11 +55,9 @@ export class Build { */ async build() { - const outFile = this.outputPath(); - const rendered = await renderSass({ file: this.source, - outFile, + outFile: this.targetPath, sourceMap: true, sourceMapEmbed: true, includePaths: [ @@ -78,7 +70,8 @@ export class Build { this.includedFiles = rendered.stats.includedFiles; - await writeFile(outFile, prefixed.css); + await mkdirpAsync(path.dirname(this.targetPath)); + await writeFile(this.targetPath, prefixed.css); return this; } diff --git a/src/server/sass/build.test.js b/src/server/sass/build.test.js index fbc3ede3a355..f77db1cba976 100644 --- a/src/server/sass/build.test.js +++ b/src/server/sass/build.test.js @@ -17,34 +17,35 @@ * under the License. */ -import path from 'path'; -import sass from 'node-sass'; +import { resolve } from 'path'; +import { readFileSync } from 'fs'; + +import del from 'del'; + import { Build } from './build'; -jest.mock('node-sass'); +const TMP = resolve(__dirname, '__tmp__'); +const FIXTURE = resolve(__dirname, '__fixtures__/index.scss'); -describe('SASS builder', () => { - jest.mock('fs'); +afterEach(async () => { + await del(TMP); +}); - it('generates a glob', () => { - const builder = new Build('/foo/style.sass'); - expect(builder.getGlob()).toEqual(path.join('/foo', '**', '*.s{a,c}ss')); - }); +it('builds SASS', async () => { + const cssPath = resolve(TMP, 'style.css'); + await (new Build(FIXTURE, { + info: () => {}, + warn: () => {}, + error: () => {}, + }, cssPath)).build(); - it('builds SASS', () => { - sass.render.mockImplementation(() => Promise.resolve(null, { css: 'test' })); - const builder = new Build('/foo/style.sass'); - builder.build(); - - const sassCall = sass.render.mock.calls[0][0]; - expect(sassCall.file).toEqual('/foo/style.sass'); - expect(sassCall.outFile).toEqual(path.join('/foo', 'style.css')); - 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')); - }); -}); \ No newline at end of file + expect(readFileSync(cssPath, 'utf8').replace(/(\/\*# sourceMappingURL=).*( \*\/)/, '$1...$2')) + .toMatchInlineSnapshot(` +"foo bar { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } +/*# sourceMappingURL=... */" +`); +}); diff --git a/src/server/sass/build_all.js b/src/server/sass/build_all.js index 08fdf9d41f68..ee449ad53297 100644 --- a/src/server/sass/build_all.js +++ b/src/server/sass/build_all.js @@ -17,16 +17,18 @@ * under the License. */ +import { resolve } from 'path'; + 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 => { if (!styleSheet.localPath.endsWith('.scss')) { return; } - const bundle = new Build(styleSheet.localPath, log); + const bundle = new Build(styleSheet.localPath, log, resolve(buildDir, styleSheet.publicPath)); await bundle.build(); return bundle; diff --git a/src/server/sass/index.js b/src/server/sass/index.js index 4def49cafc06..44a8470ace88 100644 --- a/src/server/sass/index.js +++ b/src/server/sass/index.js @@ -17,7 +17,7 @@ * under the License. */ -import { IS_KIBANA_DISTRIBUTABLE } from '../../utils'; +import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../../utils'; export async function sassMixin(kbnServer, server, config) { if (process.env.kbnWorkerType === 'optmzr') { @@ -45,7 +45,7 @@ export async function sassMixin(kbnServer, server, config) { }; try { - scssBundles = await buildAll(kbnServer.uiExports.styleSheetPaths, log); + scssBundles = await buildAll(kbnServer.uiExports.styleSheetPaths, log, fromRoot('built_assets/css')); scssBundles.forEach(bundle => { bundle.includedFiles.forEach(file => trackedFiles.add(file)); diff --git a/src/server/saved_objects/migrations/core/__snapshots__/elastic_index.test.ts.snap b/src/server/saved_objects/migrations/core/__snapshots__/elastic_index.test.ts.snap index 17db6cbe1df4..25ebffb17c58 100644 --- a/src/server/saved_objects/migrations/core/__snapshots__/elastic_index.test.ts.snap +++ b/src/server/saved_objects/migrations/core/__snapshots__/elastic_index.test.ts.snap @@ -1,5 +1,23 @@ // 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`] = ` Array [ "bulk", diff --git a/src/server/saved_objects/migrations/core/elastic_index.test.ts b/src/server/saved_objects/migrations/core/elastic_index.test.ts index f66c021e81e6..fcaaf246656b 100644 --- a/src/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/server/saved_objects/migrations/core/elastic_index.test.ts @@ -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 () => { const callCluster = sinon.spy(async (path: string, { index }: any) => { return { diff --git a/src/server/saved_objects/migrations/core/elastic_index.ts b/src/server/saved_objects/migrations/core/elastic_index.ts index 15a6d2519951..6fd9a60cf2dc 100644 --- a/src/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/server/saved_objects/migrations/core/elastic_index.ts @@ -65,7 +65,7 @@ export async function fetchInfo(callCluster: CallCluster, index: string): Promis 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 }); } +/** + * 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 * some rudimentary expectations. Past Kibana indices had multiple root documents, etc @@ -296,7 +316,7 @@ export async function claimAlias( * * @param {FullIndexInfo} indexInfo */ -async function assertIsSupportedIndex(indexInfo: FullIndexInfo) { +function assertIsSupportedIndex(indexInfo: FullIndexInfo) { const currentTypes = getTypes(indexInfo.mappings); const isV5Index = currentTypes.length > 1 || currentTypes[0] !== ROOT_TYPE; if (isV5Index) { diff --git a/src/ui/public/doc_table/components/table_header.html b/src/ui/public/doc_table/components/table_header.html index 00ae62bace9b..455604830beb 100644 --- a/src/ui/public/doc_table/components/table_header.html +++ b/src/ui/public/doc_table/components/table_header.html @@ -1,7 +1,7 @@ diff --git a/src/ui/public/doc_table/components/table_header.js b/src/ui/public/doc_table/components/table_header.js index edaaef460b02..200a7d4de7da 100644 --- a/src/ui/public/doc_table/components/table_header.js +++ b/src/ui/public/doc_table/components/table_header.js @@ -36,7 +36,9 @@ module.directive('kbnTableHeader', function (shortDotsFilter) { onMoveColumn: '=?', }, template: headerHtml, - controller: function ($scope) { + controller: function ($scope, config) { + $scope.hideTimeColumn = config.get('doc_table:hideTimeColumn'); + $scope.isSortableColumn = function isSortableColumn(columnName) { return ( !!$scope.indexPattern diff --git a/src/ui/public/doc_table/components/table_row.js b/src/ui/public/doc_table/components/table_row.js index 06cc47d4203e..b7c5768bbaa9 100644 --- a/src/ui/public/doc_table/components/table_row.js +++ b/src/ui/public/doc_table/components/table_row.js @@ -45,7 +45,7 @@ const MIN_LINE_LENGTH = 20; * * ``` */ -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 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; - if (indexPattern.timeFieldName) { + const hideTimeColumn = config.get('doc_table:hideTimeColumn'); + if (indexPattern.timeFieldName && !hideTimeColumn) { newHtmls.push(cellTemplate({ timefield: true, formatted: _displayField(row, indexPattern.timeFieldName), diff --git a/src/ui/ui_exports/ui_export_types/style_sheet_paths.js b/src/ui/ui_exports/ui_export_types/style_sheet_paths.js index 81644a494288..016b1b1dd32d 100644 --- a/src/ui/ui_exports/ui_export_types/style_sheet_paths.js +++ b/src/ui/ui_exports/ui_export_types/style_sheet_paths.js @@ -18,6 +18,7 @@ */ import path from 'path'; +import { existsSync } from 'fs'; import { flatConcatAtType } from './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 - let relativePath = path.relative(publicDir, localPath); + let relativePath = path.relative(publicDir, localCssPath); // replace back slashes on windows 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}`; return { @@ -64,4 +71,4 @@ function normalize(localPath, type, pluginSpec) { }; } -export const styleSheetPaths = wrap(mapSpec(normalize), flatConcatAtType); \ No newline at end of file +export const styleSheetPaths = wrap(mapSpec(normalize), flatConcatAtType); diff --git a/src/ui/ui_render/ui_render_mixin.js b/src/ui/ui_render/ui_render_mixin.js index 833588a6de53..7ecd0d7af142 100644 --- a/src/ui/ui_render/ui_render_mixin.js +++ b/src/ui/ui_render/ui_render_mixin.js @@ -23,6 +23,7 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; import { AppBootstrap } from './bootstrap'; import { mergeVariables } from './lib'; +import { fromRoot } from '../../utils'; export function uiRenderMixin(kbnServer, server, config) { function replaceInjectedVars(request, injectedVars) { @@ -50,6 +51,9 @@ export function uiRenderMixin(kbnServer, server, config) { // render all views from ./views server.setupViews(resolve(__dirname, 'views')); + // expose built css + server.exposeStaticDir('/built_assets/css/{path*}', fromRoot('built_assets/css')); + server.route({ path: '/bundles/app/{id}/bootstrap.js', method: 'GET', @@ -63,12 +67,19 @@ export function uiRenderMixin(kbnServer, server, config) { const basePath = config.get('server.basePath'); const regularBundlePath = `${basePath}/bundles`; - const dllBundlePath = `${basePath}/dlls`; + const dllBundlePath = `${basePath}/built_assets/dlls`; const styleSheetPaths = [ `${dllBundlePath}/vendors.style.dll.css`, `${regularBundlePath}/commons.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({ templateData: { diff --git a/tasks/config/karma.js b/tasks/config/karma.js index d2ca40cef2b3..5862d69b9fcc 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -85,17 +85,17 @@ module.exports = function (grunt) { // list of files / patterns to load in the browser 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/dlls/vendors.style.dll.css', + 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', 'http://localhost:5610/bundles/tests.style.css' ], proxies: { '/tests/': 'http://localhost:5610/tests/', '/bundles/': 'http://localhost:5610/bundles/', - '/dlls/': 'http://localhost:5610/dlls/' + '/built_assets/dlls/': 'http://localhost:5610/built_assets/dlls/' }, client: { @@ -176,10 +176,10 @@ module.exports = function (grunt) { singleRun: true, options: { 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/dlls/vendors.style.dll.css', + 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', 'http://localhost:5610/bundles/tests.style.css' ] } diff --git a/x-pack/plugins/apm/public/components/app/Main/Home.tsx b/x-pack/plugins/apm/public/components/app/Main/Home.tsx index 53b2be947688..ef185c5f072d 100644 --- a/x-pack/plugins/apm/public/components/app/Main/Home.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/Home.tsx @@ -20,12 +20,12 @@ const homeTabs: IHistoryTab[] = [ { path: '/services', name: 'Services', - component: ServiceOverview + render: props => }, { path: '/traces', name: 'Traces', - component: TraceOverview + render: props => } ]; diff --git a/x-pack/plugins/apm/public/components/app/Main/__test__/__snapshots__/Home.test.js.snap b/x-pack/plugins/apm/public/components/app/Main/__test__/__snapshots__/Home.test.js.snap index 87451ecb3743..970d3692b0a8 100644 --- a/x-pack/plugins/apm/public/components/app/Main/__test__/__snapshots__/Home.test.js.snap +++ b/x-pack/plugins/apm/public/components/app/Main/__test__/__snapshots__/Home.test.js.snap @@ -39,14 +39,14 @@ exports[`Home component should render 1`] = ` tabs={ Array [ Object { - "component": [Function], "name": "Services", "path": "/services", + "render": [Function], }, Object { - "component": [Function], "name": "Traces", "path": "/traces", + "render": [Function], }, ] } diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx index 435085afe5dd..d83b07d325ab 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx @@ -26,7 +26,7 @@ export class ServiceDetailTabs extends React.Component { name: 'Transactions', path: `/${serviceName}/transactions/${transactionTypes[0]}`, routePath: `/${serviceName}/transactions/:transactionType?`, - component: () => ( + render: () => ( { { name: 'Errors', path: `/${serviceName}/errors`, - component: () => { + render: () => { return ( ); @@ -45,7 +45,7 @@ export class ServiceDetailTabs extends React.Component { { name: 'Metrics', path: `/${serviceName}/metrics`, - component: () => + render: () => } ]; diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout.tsx index 9cf82a08cde2..c69ae43c627c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout.tsx @@ -90,26 +90,19 @@ export class MachineLearningFlyout extends Component { }; public addErrorToast = () => { - const { location, urlParams } = this.props; - const { serviceName = 'unknown', transactionType } = urlParams; + const { urlParams } = this.props; + const { serviceName = 'unknown' } = urlParams; if (!serviceName) { return; } toastNotifications.addWarning({ - title: 'Job already exists', + title: 'Job creation failed', text: (

- There's already a job running for anomaly detection on{' '} - {serviceName} ({transactionType}).{' '} - - View existing job - + Your current license may not allow for creating machine learning jobs, + or this job may already exist.

) }); diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx index e587f513a58b..b29387f11933 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx @@ -70,12 +70,13 @@ const traceListColumns: ITableColumn[] = [ ]; export function TraceList({ items = [], noItemsMessage, isLoading }: Props) { - return isLoading ? null : ( + const noItems = isLoading ? null : noItemsMessage; + return ( ); diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap b/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap index 1f38d5961e4b..d960d846a1da 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/__jest__/__snapshots__/TransactionOverview.test.js.snap @@ -6,6 +6,7 @@ exports[`TransactionOverviewView should render with type filter controls 1`] = ` describedByIds={Array []} fullWidth={false} hasEmptyLabelSpace={false} + label="Filter by type" > {serviceTransactionTypes.length > 1 ? ( - + ({ - text: `Filter by type: ${type}`, + text: `${type}`, value: type }))} value={transactionType} diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx index 312f22d7760e..124d355e19a9 100644 --- a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx @@ -40,17 +40,17 @@ describe('HistoryTabs', () => { { name: 'One', path: '/one', - component: () => + render: props => }, { name: 'Two', path: '/two', - component: () => + render: () => }, { name: 'Three', path: '/three', - component: () => + render: () => } ]; diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap index f23eb64416aa..3e4c0b065657 100644 --- a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap @@ -55,19 +55,19 @@ exports[`HistoryTabs should render correctly 1`] = ` size="l" /> `; diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx b/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx index c5f67d440878..ff3f7e79357a 100644 --- a/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx @@ -17,7 +17,7 @@ export interface IHistoryTab { path: string; routePath?: string; name: React.ReactNode; - component?: React.SFC | React.ComponentClass; + render?: (props: RouteComponentProps) => React.ReactNode; } export interface HistoryTabsProps extends RouteComponentProps { @@ -51,10 +51,10 @@ const HistoryTabsWithoutRouter = ({ {tabs.map(tab => - tab.component ? ( + tab.render ? ( ) : null diff --git a/x-pack/plugins/apm/server/lib/status_check/agent_check.js b/x-pack/plugins/apm/server/lib/status_check/agent_check.js index 65482791e38e..accd7eb84244 100644 --- a/x-pack/plugins/apm/server/lib/status_check/agent_check.js +++ b/x-pack/plugins/apm/server/lib/status_check/agent_check.js @@ -6,6 +6,7 @@ import { PROCESSOR_NAME } from '../../../common/constants'; +// Note: this logic is duplicated in tutorials/apm/envs/on_prem export async function getAgentStatus({ setup }) { const { client, config } = setup; @@ -18,11 +19,12 @@ export async function getAgentStatus({ setup }) { size: 0, query: { bool: { - filter: { - exists: { - field: PROCESSOR_NAME - } - } + should: [ + { term: { [PROCESSOR_NAME]: 'error' } }, + { term: { [PROCESSOR_NAME]: 'transaction' } }, + { term: { [PROCESSOR_NAME]: 'metric' } }, + { term: { [PROCESSOR_NAME]: 'sourcemap' } } + ] } } } diff --git a/x-pack/plugins/apm/server/lib/status_check/server_check.js b/x-pack/plugins/apm/server/lib/status_check/server_check.js index 6df2135426d2..d13a63adda61 100644 --- a/x-pack/plugins/apm/server/lib/status_check/server_check.js +++ b/x-pack/plugins/apm/server/lib/status_check/server_check.js @@ -4,6 +4,7 @@ * 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 }) { const { client, config } = setup; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/alterColumn.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/alterColumn.js index 763fe0fe5720..a9ad20c05c6f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/alterColumn.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/alterColumn.js @@ -79,7 +79,7 @@ describe('alterColumn', () => { it('throws when converting to an invalid type', () => { 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'`); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/axis_config.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/axis_config.js index f74f568080dc..1e4bbccb8ae3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/axis_config.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/axis_config.js @@ -59,7 +59,7 @@ describe('axisConfig', () => { expect(fn) .withArgs(testTable, { position: 'foo' }) .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' }) .to.throwException(e => { 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' }) .to.throwException(e => { 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` ); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/compare.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/compare.js index c7b61be4e4b2..2bd31f5d69ff 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/compare.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/compare.js @@ -24,10 +24,14 @@ describe('compare', () => { it('throws when invalid op is provided', () => { 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(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.` + ); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/font.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/font.js index b05945c500e1..0bd5871b03ad 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/font.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/font.js @@ -105,7 +105,7 @@ describe('font', () => { it('throws when provided an invalid weight', () => { 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) .withArgs(null, { align: 'foo' }) .to.throwException(e => { - expect(e.message).to.be('Invalid text alignment: foo'); + expect(e.message).to.be(`Invalid text alignment: 'foo'`); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/getCell.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/getCell.js index 9509c05f9229..7b73dbec4df0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/getCell.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/getCell.js @@ -44,7 +44,7 @@ describe('getCell', () => { it('throws when invalid column is provided', () => { 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; 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(e.message).to.be('Row not found: 0'); + expect(e.message).to.be(`Row not found: '0'`); }); expect(() => fn(emptyTable)).to.throwException(e => { - expect(e.message).to.be('Row not found: 0'); + expect(e.message).to.be(`Row not found: '0'`); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/ply.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/ply.js index 0e72cb38885b..54e33de91a47 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/ply.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/ply.js @@ -82,12 +82,12 @@ describe('ply', () => { it('throws when by is an invalid column', () => { expect(() => fn(testTable, { by: [''], expression: [averagePrice] })).to.throwException( 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( e => { - expect(e.message).to.be('No such column: foo'); + expect(e.message).to.be(`Column not found: 'foo'`); } ); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js index 5b16b80ddd74..61df4292e025 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/progress.js @@ -29,7 +29,7 @@ describe('progress', () => { expect(fn) .withArgs(3) .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) .withArgs(value, { max: -0.5 }) .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`); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/reveal_image.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/reveal_image.js index 3dc00b3c5559..3a3707e7c0b7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/reveal_image.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/reveal_image.js @@ -29,7 +29,7 @@ describe('revealImage', () => { origin: 'top', }) .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) @@ -39,7 +39,9 @@ describe('revealImage', () => { origin: 'top', }) .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` + ); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/timefilter.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/timefilter.js index a63adc3d0533..70a3a007b889 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/timefilter.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/__tests__/timefilter.js @@ -88,7 +88,7 @@ describe('timefilter', () => { it('throws when provided an invalid date string', () => { 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'`); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.js index 0c706e4d562b..c12056488540 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.js @@ -74,7 +74,7 @@ export const alterColumn = () => ({ case 'null': return () => null; default: - throw new Error(`Cannot convert to ${type}`); + throw new Error(`Cannot convert to '${type}'`); } })(); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.js index a89f2206023a..f2672552f0c3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.js @@ -43,7 +43,7 @@ export const axisConfig = () => ({ fn: (context, args) => { const positions = ['top', 'bottom', 'left', 'right', '']; 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; @@ -51,16 +51,16 @@ export const axisConfig = () => ({ if (min != null && isNaN(min)) { throw new Error( - `Invalid date string '${ + `Invalid date string: '${ 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)) { throw new Error( - `Invalid date string '${ + `Invalid date string: '${ 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` ); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.js index a0c117af7dd8..43d386229e3b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.js @@ -61,7 +61,7 @@ export const compare = () => ({ } return false; 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; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/font.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/font.js index ec99f19d8d59..7e9a3e9887f2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/font.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/font.js @@ -80,10 +80,10 @@ export const font = () => ({ }, fn: (context, args) => { 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)) { - 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 diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.js index e978c6d64c9f..28232ab0035b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/getCell.js @@ -26,14 +26,14 @@ export const getCell = () => ({ fn: (context, args) => { const row = context.rows[args.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 value = row[column]; if (typeof value === 'undefined') { - throw new Error(`Column not found: ${column}`); + throw new Error(`Column not found: '${column}'`); } return value; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.js index c082c5c4c988..94644d976180 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.js @@ -95,7 +95,7 @@ export const ply = () => ({ byColumns = args.by.map(by => { const column = context.columns.find(column => column.name === by); if (!column) { - throw new Error(`No such column: ${by}`); + throw new Error(`Column not found: '${by}'`); } return column; }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.js index 863e606984f7..d78fe4b062aa 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.js @@ -72,10 +72,10 @@ export const progress = () => ({ }, fn: (value, args) => { 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) { - 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 = ''; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.js index 888f0899978d..a57830bdf9a5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.js @@ -35,7 +35,7 @@ export const revealImage = () => ({ }, fn: (percent, args) => { 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 { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.js index f9a8b12497ac..88a89233b222 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/timefilter.js @@ -50,7 +50,7 @@ export const timefilter = () => ({ const moment = dateMath.parse(str); if (!moment || !moment.isValid()) { - throw new Error(`Invalid date/time string ${str}`); + throw new Error(`Invalid date/time string: '${str}'`); } return moment.toISOString(); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/__tests__/demodata.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/__tests__/demodata.js index 86ca52ebbc4b..1871bc560b79 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/__tests__/demodata.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/__tests__/demodata.js @@ -36,7 +36,7 @@ describe('demodata', () => { expect(fn) .withArgs(null, { type: 'foo' }) .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'."); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/demodata/get_demo_rows.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/demodata/get_demo_rows.js index 78bb441e242c..e61d800e9e86 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/demodata/get_demo_rows.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/src/demodata/get_demo_rows.js @@ -15,5 +15,5 @@ export function getDemoRows(arg) { if (arg === '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'.`); } diff --git a/x-pack/plugins/canvas/common/lib/fonts.ts b/x-pack/plugins/canvas/common/lib/fonts.ts index 69b3bf2bb71b..b413e1c74d2b 100644 --- a/x-pack/plugins/canvas/common/lib/fonts.ts +++ b/x-pack/plugins/canvas/common/lib/fonts.ts @@ -6,52 +6,53 @@ export const americanTypewriter = { 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 = { label: 'Baskerville', - value: `Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif`, + value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", }; export const bookAntiqua = { 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 = { 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 = { 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 = { 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 = { label: 'Helvetica Neue', - value: `'Helvetica Neue', Helvetica, Arial, sans-serif`, + value: "'Helvetica Neue', Helvetica, Arial, sans-serif", }; export const hoeflerText = { 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 = { 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 openSans = { label: 'Open Sans', value: `'Open Sans', 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 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 = { 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 = [ americanTypewriter, diff --git a/x-pack/plugins/canvas/public/apps/workpad/routes.js b/x-pack/plugins/canvas/public/apps/workpad/routes.js index 9d8af8cd11b9..727f03a779da 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/routes.js +++ b/x-pack/plugins/canvas/public/apps/workpad/routes.js @@ -33,7 +33,7 @@ export const routes = [ 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 // https://github.com/elastic/kibana/issues/20277 - if (err.response.status === 403) { + if (err.response && err.response.status === 403) { dispatch(setCanUserWrite(false)); } 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 // https://github.com/elastic/kibana/issues/20277 workpadService.update(params.id, fetchedWorkpad).catch(err => { - if (err.response.status === 403) { + if (err.response && err.response.status === 403) { dispatch(setCanUserWrite(false)); } }); diff --git a/x-pack/plugins/canvas/public/components/clipboard/clipboard.js b/x-pack/plugins/canvas/public/components/clipboard/clipboard.js index 72ed24dc5f10..1b516fdc3e9f 100644 --- a/x-pack/plugins/canvas/public/components/clipboard/clipboard.js +++ b/x-pack/plugins/canvas/public/components/clipboard/clipboard.js @@ -28,7 +28,7 @@ export class Clipboard extends React.PureComponent { render() { return ( -
+
{this.props.children}
); diff --git a/x-pack/plugins/canvas/public/components/clipboard/clipboard.scss b/x-pack/plugins/canvas/public/components/clipboard/clipboard.scss new file mode 100644 index 000000000000..206b5eaf760e --- /dev/null +++ b/x-pack/plugins/canvas/public/components/clipboard/clipboard.scss @@ -0,0 +1,3 @@ +.canvasClipboard { + cursor: pointer; +} diff --git a/x-pack/plugins/canvas/public/components/download/__tests__/download.js b/x-pack/plugins/canvas/public/components/download/__tests__/download.js index 2d2b40236fdf..36c3a199e6fc 100644 --- a/x-pack/plugins/canvas/public/components/download/__tests__/download.js +++ b/x-pack/plugins/canvas/public/components/download/__tests__/download.js @@ -10,13 +10,13 @@ import { render } from 'enzyme'; import { Download } from '../'; describe('', () => { - it('has canvas_download class', () => { + it('has canvasDownload class', () => { const wrapper = render( ); - expect(wrapper.hasClass('canvas_download')).to.be.ok; + expect(wrapper.hasClass('canvasDownload')).to.be.ok; }); }); diff --git a/x-pack/plugins/canvas/public/components/download/download.js b/x-pack/plugins/canvas/public/components/download/download.js index e2b39f15d48d..835ea3d14abf 100644 --- a/x-pack/plugins/canvas/public/components/download/download.js +++ b/x-pack/plugins/canvas/public/components/download/download.js @@ -28,7 +28,7 @@ export class Download extends React.PureComponent { render() { return ( -
+
{this.props.children}
); diff --git a/x-pack/plugins/canvas/public/components/workpad_export/index.js b/x-pack/plugins/canvas/public/components/workpad_export/index.js index a54acaca0140..897a2e06e436 100644 --- a/x-pack/plugins/canvas/public/components/workpad_export/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_export/index.js @@ -13,6 +13,7 @@ import { getWorkpad, getPages } from '../../state/selectors/workpad'; import { getReportingBrowserType } from '../../state/selectors/app'; import { notify } from '../../lib/notify'; import { getWindow } from '../../lib/get_window'; +import { downloadWorkpad } from '../../lib/download_workpad'; import { WorkpadExport as Component } from './workpad_export'; import { getPdfUrl, createPdf } from './utils'; @@ -43,29 +44,34 @@ export const WorkpadExport = compose( throw new Error(`Unknown export type: ${type}`); }, onCopy: type => { - if (type === 'pdf') { - return notify.info('The PDF generation URL was copied to your clipboard.'); + switch (type) { + 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}`); }, onExport: type => { - if (type === 'pdf') { - return createPdf(workpad, { pageCount }) - .then(({ data }) => { - notify.info('Exporting PDF. You can track the progress in Management.', { - title: `PDF export of workpad '${workpad.name}'`, + switch (type) { + case 'pdf': + return createPdf(workpad, { pageCount }) + .then(({ data }) => { + 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}'` }); }); - - // 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': + return downloadWorkpad(workpad.id); + default: + throw new Error(`Unknown export type: ${type}`); } - - throw new Error(`Unknown export type: ${type}`); }, })) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.js b/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.js index 7056e6250789..9b36b2e1f479 100644 --- a/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.js +++ b/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.js @@ -9,12 +9,12 @@ import PropTypes from 'prop-types'; import { EuiButton, EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, EuiSpacer, EuiCodeBlock, - EuiHorizontalRule, - EuiFormRow, + EuiCode, + EuiContextMenu, + EuiIcon, + EuiText, } from '@elastic/eui'; import { Popover } from '../popover'; import { Clipboard } from '../clipboard'; @@ -27,81 +27,155 @@ export class WorkpadExport extends React.PureComponent { 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 = () => { this.props.onExport('pdf'); }; - renderControls = closePopover => { + downloadWorkpad = () => { + this.props.onExport('json'); + }; + + renderPDFControls = closePopover => { const pdfUrl = this.props.getExportUrl('pdf'); return ( -
- - - - { - this.exportPdf(); - closePopover(); - }} - > - Export as PDF - - - - - - - - - - {pdfUrl} - - +
+ +

PDFs can take a minute or two to generate based upon the size of your workpad

+
+ - - { - this.props.onCopy('pdf'); - closePopover(); - }} - > - - - - - + {this.props.options} + + { + closePopover(); + this.exportPdf(); + }} + size="s" + style={{ width: '100%' }} + > + Generate PDF + + + + +

+ Alternatively, copy this POST URL to call generation from outside Kibana or from + Watcher. +

+
+ + + { + this.props.onCopy('pdf'); + closePopover(); + }} + > + + Copy POST URL + +
); }; + renderPanelTree = closePopover => ({ + id: 0, + title: 'Share this workpad', + items: [ + { + name: 'Download as JSON', + icon: , + 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 = () => { + const reportingConfig = `xpack.reporting: + enabled: true + capture.browser.type: chromium`; + return ( -
- Export to PDF is disabled. You must configure reporting to use the Chromium browser. Add - this to your kibana.yml file. +
+ +

+ Export to PDF is disabled. You must configure reporting to use the Chromium browser. Add + this to your kibana.yml file. +

+
- - xpack.reporting.capture.browser.type: chromium - + this.props.onCopy('reportingConfig')}> + + {reportingConfig} + +
); }; render() { const exportControl = togglePopover => ( - + ); + // TODO: replace this with `showShareContextMenu` in `ui/share` once it's been converted to React return ( - + {({ closePopover }) => ( - - - {this.props.enabled && this.renderControls(closePopover)} - {!this.props.enabled && this.renderDisabled()} - - + )} ); diff --git a/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.scss b/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.scss new file mode 100644 index 000000000000..a35db90e963c --- /dev/null +++ b/x-pack/plugins/canvas/public/components/workpad_export/workpad_export.scss @@ -0,0 +1,10 @@ +.canvasWorkpadExport__panelContent { + padding: $euiSize; +} +.canvasWorkpadExport__reportingConfig { + .euiCodeBlock__pre { + @include euiScrollBar; + overflow-x: auto; + white-space: pre; + } +} diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/plugins/canvas/public/components/workpad_loader/index.js index 8e83819054fd..31ea8f7d41e5 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/index.js @@ -7,13 +7,13 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { compose, withState, getContext, withHandlers } from 'recompose'; -import fileSaver from 'file-saver'; import * as workpadService from '../../lib/workpad_service'; import { notify } from '../../lib/notify'; import { canUserWrite } from '../../state/selectors/app'; import { getWorkpad } from '../../state/selectors/workpad'; import { getId } from '../../lib/get_id'; import { setCanUserWrite } from '../../state/actions/transient'; +import { downloadWorkpad } from '../../lib/download_workpad'; import { WorkpadLoader as Component } from './workpad_loader'; const mapStateToProps = state => ({ @@ -46,7 +46,7 @@ export const WorkpadLoader = compose( 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 // https://github.com/elastic/kibana/issues/20277 - if (err.response.status === 403) { + if (err.response && err.response.status === 403) { props.setCanUserWrite(false); } } @@ -67,15 +67,7 @@ export const WorkpadLoader = compose( }, // Workpad import/export methods - 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` }); - } - }, + downloadWorkpad: () => workpadId => downloadWorkpad(workpadId), // Clone workpad given an id cloneWorkpad: props => async workpadId => { @@ -89,7 +81,7 @@ export const WorkpadLoader = compose( 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 // https://github.com/elastic/kibana/issues/20277 - if (err.response.status === 403) { + if (err.response && err.response.status === 403) { props.setCanUserWrite(false); } } @@ -122,7 +114,7 @@ export const WorkpadLoader = compose( errors.push(result.id); // 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 - if (result.err.response.status === 403) { + if (result.err.response && result.err.response.status === 403) { props.setCanUserWrite(false); } } else { diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 66def18abaf8..9008ae94a232 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -142,7 +142,7 @@ export class WorkpadLoader extends React.PureComponent { this.props.downloadWorkpad(workpad.id)} aria-label="Download Workpad" /> @@ -288,7 +288,7 @@ export class WorkpadLoader extends React.PureComponent { ); const downloadButton = ( - + {`Download (${selectedWorkpads.length})`} ); diff --git a/x-pack/plugins/canvas/public/lib/download_workpad.js b/x-pack/plugins/canvas/public/lib/download_workpad.js new file mode 100644 index 000000000000..20cd4ad46ce0 --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/download_workpad.js @@ -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` }); + } +}; diff --git a/x-pack/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/plugins/canvas/public/state/middleware/es_persist.js index 34af6b4fc839..5f906010e75b 100644 --- a/x-pack/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/plugins/canvas/public/state/middleware/es_persist.js @@ -55,26 +55,26 @@ export const esPersistMiddleware = ({ getState }) => { if (workpadChanged(curState, newState) || assetsChanged(curState, newState)) { const persistedWorkpad = getWorkpadPersisted(getState()); return update(persistedWorkpad.id, persistedWorkpad).catch(err => { - if (err.response.status === 400) { - return notify.error(err.response, { - title: `Couldn't save your changes to Elasticsearch`, - }); - } - - 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.`, - { + const statusCode = err.response && err.response.status; + switch (statusCode) { + case 400: + return notify.error(err.response, { 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`, - }); }); } }; diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index 300a060040e1..c37755ba595e 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -24,6 +24,7 @@ @import '../components/autocomplete/autocomplete'; @import '../components/border_connection/border_connection'; @import '../components/border_resize_handle/border_resize_handle'; +@import '../components/clipboard/clipboard'; @import '../components/color_dot/color_dot'; @import '../components/color_palette/color_palette'; @import '../components/color_picker_mini/color_picker_mini'; @@ -52,6 +53,7 @@ @import '../components/toolbar/toolbar'; @import '../components/toolbar/tray/tray'; @import '../components/workpad/workpad'; +@import '../components/workpad_export/workpad_export'; @import '../components/workpad_loader/workpad_loader'; @import '../components/workpad_loader/workpad_dropzone/workpad_dropzone'; @import '../components/workpad_page/workpad_page'; diff --git a/x-pack/plugins/canvas/server/routes/workpad.js b/x-pack/plugins/canvas/server/routes/workpad.js index 8042a82aedbd..cfc53ac4b85d 100644 --- a/x-pack/plugins/canvas/server/routes/workpad.js +++ b/x-pack/plugins/canvas/server/routes/workpad.js @@ -41,7 +41,7 @@ export function workpad(server) { const savedObjectsClient = req.getSavedObjectsClient(); 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(); diff --git a/x-pack/plugins/gis/public/components/layer_panel/join_editor/index.js b/x-pack/plugins/gis/public/components/layer_panel/join_editor/index.js index 6aabd1fd3b47..957382d3f7ac 100644 --- a/x-pack/plugins/gis/public/components/layer_panel/join_editor/index.js +++ b/x-pack/plugins/gis/public/components/layer_panel/join_editor/index.js @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import { JoinEditor } from './view'; +import { getSelectedLayer, getSelectedLayerJoinDescriptors } from '../../../selectors/map_selectors'; import { setJoinsForLayer } from '../../../actions/store_actions'; function mapDispatchToProps(dispatch) { @@ -16,12 +17,10 @@ function mapDispatchToProps(dispatch) { }; } -function mapStateToProps({}, props) { +function mapStateToProps(state = {}) { return { - joins: props.layer.getJoins().map(join => { - return join.toDescriptor(); - }), - layer: props.layer, + joins: getSelectedLayerJoinDescriptors(state), + layer: getSelectedLayer(state), }; } diff --git a/x-pack/plugins/gis/public/components/layer_panel/view.js b/x-pack/plugins/gis/public/components/layer_panel/view.js index ff14a5762d74..7bdd6edf3776 100644 --- a/x-pack/plugins/gis/public/components/layer_panel/view.js +++ b/x-pack/plugins/gis/public/components/layer_panel/view.js @@ -49,7 +49,7 @@ export class LayerPanel extends React.Component { return ( - + ); } diff --git a/x-pack/plugins/gis/public/selectors/map_selectors.js b/x-pack/plugins/gis/public/selectors/map_selectors.js index a9c74677bfb1..a9f6dbc10245 100644 --- a/x-pack/plugins/gis/public/selectors/map_selectors.js +++ b/x-pack/plugins/gis/public/selectors/map_selectors.js @@ -135,15 +135,6 @@ export const getDataFilters = createSelector( 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( getLayerListRaw, getDataSources, @@ -152,4 +143,19 @@ export const getLayerList = createSelector( 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())); diff --git a/x-pack/plugins/gis/public/shared/layers/sources/all_sources.js b/x-pack/plugins/gis/public/shared/layers/sources/all_sources.js index f65862c4241d..3b57e583d29b 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/all_sources.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/all_sources.js @@ -21,7 +21,7 @@ export const ALL_SOURCES = [ EMSFileSource, EMSTMSSource, KibanaRegionmapSource, + KibanaTilemapSource, XYZTMSSource, WMSSource, - KibanaTilemapSource ]; diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js index 5f490239c2dd..cd074ff53c54 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js @@ -20,7 +20,7 @@ import { emsServiceSettings } from '../../../kibana_services'; export class EMSFileSource extends VectorSource { static type = 'EMS_FILE'; - static typeDisplayName = 'Elastic Maps Service region boundaries'; + static typeDisplayName = 'Elastic Maps Service vector shapes'; static createDescriptor(id) { return { @@ -60,7 +60,9 @@ export class EMSFileSource extends VectorSource { {EMSFileSource.typeDisplayName} -

Political boundry vectors hosted by EMS.

+

+ Vector shapes of administrative boundaries from Elastic Maps Service +

); @@ -108,10 +110,6 @@ export class EMSFileSource extends VectorSource { } - async isTimeAware() { - return false; - } - canFormatFeatureProperties() { return true; } diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source.js index 72b2f4a3081f..304b67c3e25f 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source.js @@ -18,7 +18,7 @@ import _ from 'lodash'; export class EMSTMSSource extends TMSSource { static type = 'EMS_TMS'; - static typeDisplayName = 'Elastic Maps Service Tile Service'; + static typeDisplayName = 'Elastic Maps Service tiles'; static createDescriptor(serviceId) { return { @@ -58,7 +58,9 @@ export class EMSTMSSource extends TMSSource { {EMSTMSSource.typeDisplayName} -

Tile services hosted by EMS.

+

+ Map tiles from Elastic Maps Service +

); diff --git a/x-pack/plugins/gis/public/shared/layers/sources/es_geohashgrid_source/es_geohashgrid_source.js b/x-pack/plugins/gis/public/shared/layers/sources/es_geohashgrid_source/es_geohashgrid_source.js index f7512ea8c570..5711a87079c0 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/es_geohashgrid_source/es_geohashgrid_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/es_geohashgrid_source/es_geohashgrid_source.js @@ -91,8 +91,7 @@ export class ESGeohashGridSource extends VectorSource {

- Group documents into grid cells and display metrics for each cell. - Great for displaying large datasets. + Group geospatial data in grids with metrics for each gridded cell

diff --git a/x-pack/plugins/gis/public/shared/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/gis/public/shared/layers/sources/es_search_source/es_search_source.js index d8fa28287440..964f23ca9751 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/es_search_source/es_search_source.js @@ -50,7 +50,7 @@ export class ESSearchSource extends VectorSource {

- Display documents from an elasticsearch index. + Geospatial data from an Elasticsearch index

); diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source.js index 321a9a2ca732..162bae2903cd 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source.js @@ -16,7 +16,7 @@ import { export class KibanaRegionmapSource extends VectorSource { static type = 'REGIONMAP_FILE'; - static typeDisplayName = 'Custom region boundaries'; + static typeDisplayName = 'Custom vector shapes'; constructor(descriptor, { ymlFileLayers }) { super(descriptor); @@ -62,7 +62,7 @@ export class KibanaRegionmapSource extends VectorSource {

- Region map boundary layers configured in your config/kibana.yml file. + Vector shapes from static files configured in kibana.yml.

diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source.js index 82f2e81dcb21..4d44d1f989e2 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source.js @@ -16,7 +16,7 @@ import { export class KibanaTilemapSource extends TMSSource { static type = 'KIBANA_TILEMAP'; - static typeDisplayName = 'TMS from kibana.yml'; + static typeDisplayName = 'Custom Tile Map Service'; static createDescriptor(url) { return { @@ -41,7 +41,9 @@ export class KibanaTilemapSource extends TMSSource { {KibanaTilemapSource.typeDisplayName} -

TMS service configured in kibana.yml

+

+ Map tiles configured in kibana.yml +

); diff --git a/x-pack/plugins/gis/public/shared/layers/sources/vector_source.js b/x-pack/plugins/gis/public/shared/layers/sources/vector_source.js index 92d5d5da8bfe..6f4ad01f01b2 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/vector_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/vector_source.js @@ -57,17 +57,14 @@ export class VectorSource extends ASource { } isFilterByMapBounds() { - console.warn('Should implement VectorSource#isFilterByMapBounds'); return false; } async getNumberFields() { - console.warn('Should implement VectorSource#getNumberFields'); return []; } async getStringFields() { - console.warn('Should implement VectorSource@getStringFields'); return []; } @@ -93,7 +90,7 @@ export class VectorSource extends ASource { } async isTimeAware() { - throw new Error('Should implement'); + return false; } } diff --git a/x-pack/plugins/gis/public/shared/layers/sources/wms_source.js b/x-pack/plugins/gis/public/shared/layers/sources/wms_source.js index a4b2dc9e61b1..85021fea39cf 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/wms_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/wms_source.js @@ -20,7 +20,7 @@ export class WMSSource extends TMSSource { static type = 'WMS'; - static typeDisplayName = 'WMS'; + static typeDisplayName = 'Web Map Service'; static createDescriptor({ serviceUrl, layers, styles }) { return { @@ -46,7 +46,9 @@ export class WMSSource extends TMSSource { {WMSSource.typeDisplayName} -

Web Map Service (WMS)

+

+ Maps from OGC Standard WMS +

); diff --git a/x-pack/plugins/gis/public/shared/layers/sources/xyz_tms_source.js b/x-pack/plugins/gis/public/shared/layers/sources/xyz_tms_source.js index f2de1bb418e6..1a266dc41029 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/xyz_tms_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/xyz_tms_source.js @@ -20,7 +20,7 @@ export class XYZTMSSource extends TMSSource { static type = 'EMS_XYZ'; - static typeDisplayName = 'TMS XYZ'; + static typeDisplayName = 'Tile Map Service from URL'; static createDescriptor(urlTemplate) { return { @@ -44,7 +44,9 @@ export class XYZTMSSource extends TMSSource { {XYZTMSSource.typeDisplayName} -

Tile Map Service with XYZ url.

+

+ Map tiles from a URL that includes the XYZ coordinates +

); diff --git a/x-pack/plugins/gis/public/shared/layers/vector_layer.js b/x-pack/plugins/gis/public/shared/layers/vector_layer.js index 57fe4688c683..7f551ddd6a32 100644 --- a/x-pack/plugins/gis/public/shared/layers/vector_layer.js +++ b/x-pack/plugins/gis/public/shared/layers/vector_layer.js @@ -223,7 +223,7 @@ export class VectorLayer extends ALayer { join: join }; } - startLoading(sourceDataId, requestToken, { timeFilters: dataFilters.timeFilters }); + startLoading(sourceDataId, requestToken, dataFilters); const leftSourceName = await this.getSourceName(); const { rawData, diff --git a/x-pack/plugins/index_management/server/lib/enrich_response.js b/x-pack/plugins/index_management/server/lib/enrich_response.js index be9661792304..6948a6610bef 100644 --- a/x-pack/plugins/index_management/server/lib/enrich_response.js +++ b/x-pack/plugins/index_management/server/lib/enrich_response.js @@ -17,5 +17,5 @@ export const enrichResponse = async (response, callWithRequest) => { // silently swallow enricher response errors } } - return response; + return enrichedResponse; }; diff --git a/x-pack/plugins/ml/public/components/anomalies_table/_anomalies_table.scss b/x-pack/plugins/ml/public/components/anomalies_table/_anomalies_table.scss index 9593fb00dc23..24aac271706c 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/_anomalies_table.scss +++ b/x-pack/plugins/ml/public/components/anomalies_table/_anomalies_table.scss @@ -75,6 +75,10 @@ min-width: 150px; } + .mlAnomalyCategoryExamples__header { + display: inline; + } + .mlAnomalyCategoryExamples__link { width: 100%; } diff --git a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js index dc100071cf25..bb9c5a5e8534 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/components/anomalies_table/anomalies_table.js @@ -29,9 +29,11 @@ import { AnomalyDetails } from './anomaly_details'; import { mlTableService } from '../../services/table_service'; import { RuleEditorFlyout } from '../../components/rule_editor'; +import { ml } from '../../services/ml_api_service'; import { INFLUENCERS_LIMIT, - ANOMALIES_TABLE_TABS + ANOMALIES_TABLE_TABS, + MAX_CHARS } from './anomalies_table_constants'; class AnomaliesTable extends Component { @@ -71,18 +73,36 @@ class AnomaliesTable extends Component { return null; } - toggleRow = (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => { + toggleRow = async (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => { const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; if (itemIdToExpandedRowMap[item.rowId]) { delete itemIdToExpandedRowMap[item.rowId]; } else { const examples = (item.entityName === 'mlcategory') ? _.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] = ( - {this.props.examples.map((example, i) => { + {(definition !== undefined && definition.terms) && + + + +

Terms

  + +
+ + {definition.terms} + +
+ +
} + {(definition !== undefined && definition.regex) && + + + +

Regex

  + +
+ + {definition.regex} + +
+ +
} + + {examples.map((example, i) => { return ( + {(i === 0 && definition !== undefined) && + +

Examples

+
} {example}
); @@ -384,6 +433,7 @@ export class AnomalyDetails extends Component { AnomalyDetails.propTypes = { anomaly: PropTypes.object.isRequired, examples: PropTypes.array, + definition: PropTypes.object, isAggregatedData: PropTypes.bool, filter: PropTypes.func, influencersLimit: PropTypes.number, diff --git a/x-pack/plugins/ml/public/components/anomalies_table/anomaly_details.test.js b/x-pack/plugins/ml/public/components/anomalies_table/anomaly_details.test.js index 6bc9f3b83b28..3f603f659040 100644 --- a/x-pack/plugins/ml/public/components/anomalies_table/anomaly_details.test.js +++ b/x-pack/plugins/ml/public/components/anomalies_table/anomaly_details.test.js @@ -6,7 +6,7 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import { AnomalyDetails } from './anomaly_details'; const props = { @@ -86,4 +86,75 @@ describe('AnomalyDetails', () => { ); 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( + + ); + + expect(wrapper.containsMatchingElement(

Regex

)).toBe(true); + expect(wrapper.containsMatchingElement(

Terms

)).toBe(true); + expect(wrapper.contains(

Examples

)).toBe(true); + }); + + test('Renders only with examples when definition prop is undefined', () => { + const categoryTabProps = { + ...props, + tabIndex: 1, + definition: undefined + }; + + const wrapper = mount( + + ); + + expect(wrapper.containsMatchingElement(

Regex

)).toBe(false); + expect(wrapper.containsMatchingElement(

Terms

)).toBe(false); + expect(wrapper.contains(

Examples

)).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( + + ); + + expect(wrapper.containsMatchingElement(

Regex

)).toBe(false); + expect(wrapper.containsMatchingElement(

Terms

)).toBe(true); + expect(wrapper.contains(

Examples

)).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( + + ); + + expect(wrapper.containsMatchingElement(

Regex

)).toBe(true); + expect(wrapper.containsMatchingElement(

Terms

)).toBe(false); + expect(wrapper.contains(

Examples

)).toBe(true); + }); }); diff --git a/x-pack/plugins/ml/public/components/job_group_select/job_group_select.js b/x-pack/plugins/ml/public/components/job_group_select/job_group_select.js index 1b413476e83a..b4c77ebe94ac 100644 --- a/x-pack/plugins/ml/public/components/job_group_select/job_group_select.js +++ b/x-pack/plugins/ml/public/components/job_group_select/job_group_select.js @@ -67,6 +67,9 @@ module.directive('mlJobGroupSelect', function () { .catch((error) => { console.log('Could not load groups from calendars', error); this.populateSelectedGroups(this.jobGroups); + }) + .then(() => { + $scope.$applyAsync(); }); }); diff --git a/x-pack/plugins/reporting/public/components/report_info_button.tsx b/x-pack/plugins/reporting/public/components/report_info_button.tsx index 56ad3f927498..2e60a49148ef 100644 --- a/x-pack/plugins/reporting/public/components/report_info_button.tsx +++ b/x-pack/plugins/reporting/public/components/report_info_button.tsx @@ -15,7 +15,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { get } from 'lodash'; +import { get, has } from 'lodash'; import React, { Component, Fragment } from 'react'; import { USES_HEADLESS_JOB_TYPES } from '../../common/constants'; import { JobInfo, jobQueueClient } from '../lib/job_queue_client'; @@ -76,6 +76,10 @@ export class ReportInfoButton extends Component { } const jobType = get(info, 'jobtype', NA); + const processedBy = + has(info, 'kibana_name') && has(info, 'kibana_id') + ? `${info.kibana_name} (${info.kibana_id})` + : UNKNOWN; // TODO queue method (clicked UI, watcher, etc) const jobInfoParts = { @@ -96,6 +100,10 @@ export class ReportInfoButton extends Component { title: 'Completed At', description: get(info, 'completed_at', NA), }, + { + title: 'Processed By', + description: processedBy, + }, { title: 'Browser Timezone', description: get(info, 'payload.browserTimezone', NA), diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index 449785a43a71..dabcea42c983 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -45,6 +45,8 @@ interface Job { status: string; statusLabel: string; max_size_reached: boolean; + attempts: number; + max_attempts: number; } interface Props { @@ -66,31 +68,31 @@ const jobStatusLabelsMap = new Map([ [ JobStatuses.PENDING, i18n.translate('xpack.reporting.jobStatuses.pendingText', { - defaultMessage: 'pending', + defaultMessage: 'Pending', }), ], [ JobStatuses.PROCESSING, i18n.translate('xpack.reporting.jobStatuses.processingText', { - defaultMessage: 'processing', + defaultMessage: 'Processing', }), ], [ JobStatuses.COMPLETED, i18n.translate('xpack.reporting.jobStatuses.completedText', { - defaultMessage: 'completed', + defaultMessage: 'Completed', }), ], [ JobStatuses.FAILED, i18n.translate('xpack.reporting.jobStatuses.failedText', { - defaultMessage: 'failed', + defaultMessage: 'Failed', }), ], [ JobStatuses.CANCELLED, i18n.translate('xpack.reporting.jobStatuses.cancelledText', { - defaultMessage: 'cancelled', + defaultMessage: 'Cancelled', }), ], ]); @@ -206,7 +208,7 @@ class ReportListingUi extends Component {
); @@ -218,7 +220,7 @@ class ReportListingUi extends Component { ); @@ -234,7 +236,11 @@ class ReportListingUi extends Component { statusTimestamp = this.formatDate(record.completed_at); } - const statusLabel = jobStatusLabelsMap.get(status as JobStatuses) || status; + let statusLabel = jobStatusLabelsMap.get(status as JobStatuses) || status; + + if (status === JobStatuses.PROCESSING) { + statusLabel = statusLabel + ` (attempt ${record.attempts} of ${record.max_attempts})`; + } if (statusTimestamp) { return ( @@ -407,8 +413,8 @@ class ReportListingUi extends Component { this.setState({ isLoading: false, total, - jobs: jobs.map((job: JobQueueEntry) => { - return { + jobs: jobs.map( + (job: JobQueueEntry): Job => ({ id: job._id, type: job._source.jobtype, object_type: job._source.payload.type, @@ -421,8 +427,10 @@ class ReportListingUi extends Component { statusLabel: jobStatusLabelsMap.get(job._source.status as JobStatuses) || job._source.status, max_size_reached: job._source.output ? job._source.output.max_size_reached : false, - }; - }), + attempts: job._source.attempts, + max_attempts: job._source.max_attempts, + }) + ), }); } }; diff --git a/x-pack/plugins/reporting/public/lib/job_queue_client.ts b/x-pack/plugins/reporting/public/lib/job_queue_client.ts index ca5d21dccfb1..173a4e31cfef 100644 --- a/x-pack/plugins/reporting/public/lib/job_queue_client.ts +++ b/x-pack/plugins/reporting/public/lib/job_queue_client.ts @@ -21,6 +21,8 @@ export interface JobContent { } export interface JobInfo { + kibana_name: string; + kibana_id: string; browser_type: string; created_at: string; priority: number; diff --git a/x-pack/plugins/reporting/server/lib/create_workers.js b/x-pack/plugins/reporting/server/lib/create_workers.js index feaf825a3390..198ea7bcb63a 100644 --- a/x-pack/plugins/reporting/server/lib/create_workers.js +++ b/x-pack/plugins/reporting/server/lib/create_workers.js @@ -8,7 +8,10 @@ import { events as esqueueEvents } from './esqueue'; import { oncePerServer } from './once_per_server'; function createWorkersFn(server) { - const queueConfig = server.config().get('xpack.reporting.queue'); + const config = server.config(); + const queueConfig = config.get('xpack.reporting.queue'); + const kibanaName = config.get('server.name'); + const kibanaId = config.get('server.uuid'); const exportTypesRegistry = server.plugins.reporting.exportTypesRegistry; // Once more document types are added, this will need to be passed in @@ -25,6 +28,8 @@ function createWorkersFn(server) { return executeJob(payload, cancellationToken); }; const workerOptions = { + kibanaName, + kibanaId, interval: queueConfig.pollInterval, intervalErrorMultiplier: queueConfig.pollIntervalErrorMultiplier, }; diff --git a/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js b/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js index bf58039e424d..1a7c243711c4 100644 --- a/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/helpers/create_index.js @@ -53,12 +53,14 @@ const schema = { completed_at: { type: 'date' }, attempts: { type: 'short' }, max_attempts: { type: 'short' }, + kibana_name: { type: 'keyword' }, + kibana_id: { type: 'keyword' }, status: { type: 'keyword' }, output: { type: 'object', properties: { content_type: { type: 'keyword' }, - size: { type: 'keyword' }, + size: { type: 'long' }, content: { type: 'object', enabled: false } } } diff --git a/x-pack/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/worker.js index c1236b8c9cd7..8c6bd5125e67 100644 --- a/x-pack/plugins/reporting/server/lib/esqueue/worker.js +++ b/x-pack/plugins/reporting/server/lib/esqueue/worker.js @@ -49,6 +49,8 @@ export class Worker extends events.EventEmitter { super(); this.id = puid.generate(); + this.kibanaId = opts.kibanaId; + this.kibanaName = opts.kibanaName; this.queue = queue; this.client = opts.client || this.queue.client; this.jobtype = type; @@ -120,6 +122,8 @@ export class Worker extends events.EventEmitter { started_at: startTime, process_expiration: expirationTime, status: constants.JOB_STATUS_PROCESSING, + kibana_id: this.kibanaId, + kibana_name: this.kibanaName, }; return this.client.update({