{
return false;
}
- const filters = await getDataActions().createFiltersFromEvent(item.values);
+ const filters = await getDataActions().createFiltersFromValueClickAction({ data: item.values });
return Boolean(filters.length);
};
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
index ecf67ee3e017c..f33ce0395af1f 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
+++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js
@@ -83,10 +83,21 @@ export class Handler {
// memoize so that the same function is returned every time,
// allowing us to remove/re-add the same function
- this.getProxyHandler = _.memoize(function(event) {
+ this.getProxyHandler = _.memoize(function(eventType) {
const self = this;
- return function(e) {
- self.vis.emit(event, e);
+ return function(eventPayload) {
+ switch (eventType) {
+ case 'brush':
+ const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw');
+ if (!xRaw) return; // not sure if this is possible?
+ return self.vis.emit(eventType, {
+ table: xRaw.table,
+ range: eventPayload.range,
+ column: xRaw.column,
+ });
+ case 'click':
+ return self.vis.emit(eventType, eventPayload);
+ }
};
});
diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss
index aaed52f8b120a..b36e62297cc23 100644
--- a/src/legacy/ui/public/_index.scss
+++ b/src/legacy/ui/public/_index.scss
@@ -9,7 +9,6 @@
// kbnChart__legend-isLoading
@import './accessibility/index';
-@import './chrome/index';
@import './directives/index';
@import './error_auto_create_index/index';
@import './error_url_overflow/index';
diff --git a/src/legacy/ui/public/chrome/_index.scss b/src/legacy/ui/public/chrome/_index.scss
deleted file mode 100644
index 7e6c3ebaccc5c..0000000000000
--- a/src/legacy/ui/public/chrome/_index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-@import './variables';
-
-@import './directives/index';
diff --git a/src/legacy/ui/public/chrome/_variables.scss b/src/legacy/ui/public/chrome/_variables.scss
deleted file mode 100644
index 5097fe4c9bfae..0000000000000
--- a/src/legacy/ui/public/chrome/_variables.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-$kbnGlobalNavClosedWidth: 53px;
-$kbnGlobalNavOpenWidth: 180px;
-$kbnGlobalNavLogoHeight: 70px;
-$kbnGlobalNavAppIconHeight: $euiSizeXXL + $euiSizeXS;
diff --git a/src/legacy/ui/public/chrome/directives/_index.scss b/src/legacy/ui/public/chrome/directives/_index.scss
deleted file mode 100644
index 4d00b02279116..0000000000000
--- a/src/legacy/ui/public/chrome/directives/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import './kbn_chrome';
diff --git a/src/legacy/ui/public/chrome/directives/_kbn_chrome.scss b/src/legacy/ui/public/chrome/directives/_kbn_chrome.scss
deleted file mode 100644
index b29a83848d291..0000000000000
--- a/src/legacy/ui/public/chrome/directives/_kbn_chrome.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * stretch the root element of the Kibana application to set the base-size that
- * flexed children should keep. Only works when paired with root styles applied
- * by core service from new platform
- */
-// SASSTODO: Naming here is too embedded and high up that changing them could cause major breaks
-#kibana-body {
- overflow-x: hidden;
- min-height: 100%;
-}
-
-.app-wrapper {
- display: flex;
- flex-flow: column nowrap;
- position: absolute;
- left: $kbnGlobalNavClosedWidth;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 5;
- margin: 0 auto;
-
- /**
- * 1. Dirty, but we need to override the .kbnGlobalNav-isOpen state
- * when we're looking at the log-in screen.
- */
- &.hidden-chrome {
- left: 0 !important; /* 1 */
- }
-
- .navbar-right {
- margin-right: 0;
- }
-}
-
-.app-wrapper-panel {
- display: flex;
- flex-grow: 1;
- flex-shrink: 0;
- flex-basis: auto;
- flex-direction: column;
-
- > * {
- flex-shrink: 0;
- }
-}
diff --git a/src/legacy/ui/public/i18n/index.tsx b/src/legacy/ui/public/i18n/index.tsx
index 4d0f5d3a5bd56..c918554563fcb 100644
--- a/src/legacy/ui/public/i18n/index.tsx
+++ b/src/legacy/ui/public/i18n/index.tsx
@@ -44,7 +44,7 @@ export function wrapInI18nContext(ComponentToWrap: React.ComponentType
) {
}
uiModules
- .get('i18n')
+ .get('i18n', ['ngSanitize'])
.provider('i18n', I18nProvider)
.filter('i18n', i18nFilter)
.directive('i18nId', i18nDirective);
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index f14f26613ef01..271586bb8c582 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -377,7 +377,8 @@ export const npStart = {
},
data: {
actions: {
- createFiltersFromEvent: Promise.resolve(['yes']),
+ createFiltersFromValueClickAction: Promise.resolve(['yes']),
+ createFiltersFromRangeSelectAction: sinon.fake(),
},
autocomplete: {
getProvider: sinon.fake(),
diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts
index 5ae2e2348aaa1..a15c7cce5511d 100644
--- a/src/legacy/ui/public/new_platform/new_platform.ts
+++ b/src/legacy/ui/public/new_platform/new_platform.ts
@@ -59,7 +59,6 @@ import {
NavigationPublicPluginSetup,
NavigationPublicPluginStart,
} from '../../../../plugins/navigation/public';
-import { VisTypeVegaSetup } from '../../../../plugins/vis_type_vega/public';
import { DiscoverSetup, DiscoverStart } from '../../../../plugins/discover/public';
import {
SavedObjectsManagementPluginSetup,
@@ -88,7 +87,6 @@ export interface PluginsSetup {
usageCollection: UsageCollectionSetup;
advancedSettings: AdvancedSettingsSetup;
management: ManagementSetup;
- visTypeVega: VisTypeVegaSetup;
discover: DiscoverSetup;
visualizations: VisualizationsSetup;
telemetry?: TelemetryPluginSetup;
diff --git a/src/legacy/ui/ui_bundles/app_entry_template.js b/src/legacy/ui/ui_bundles/app_entry_template.js
index a1c3a153a196c..683fedd34316f 100644
--- a/src/legacy/ui/ui_bundles/app_entry_template.js
+++ b/src/legacy/ui/ui_bundles/app_entry_template.js
@@ -25,6 +25,9 @@ export const appEntryTemplate = bundle => `
*
* This is programmatically created and updated, do not modify
*
+ * Any changes to this file should be kept in sync with
+ * src/core/public/entry_point.ts
+ *
* context: ${bundle.getContext()}
*/
@@ -45,7 +48,9 @@ i18n.load(injectedMetadata.i18n.translationsUrl)
browserSupportsCsp: !window.__kbnCspNotEnforced__,
requireLegacyFiles: () => {
${bundle.getRequires().join('\n ')}
- }
+ },
+ requireLegacyBootstrapModule: () => require('ui/chrome'),
+ requireNewPlatformShimModule: () => require('ui/new_platform'),
});
coreSystem
diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js
index 7afa283af83e0..79112fd687e84 100644
--- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js
+++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js
@@ -99,13 +99,6 @@ export class UiBundlesController {
this._postLoaders = [];
this._bundles = [];
- // create a bundle for core-only with no modules
- this.add({
- id: 'core',
- modules: [],
- template: appEntryTemplate,
- });
-
// create a bundle for each uiApp
for (const uiApp of uiApps) {
this.add({
diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js
index bb246d97bfe4e..35e1f8b7d2127 100644
--- a/src/legacy/ui/ui_exports/ui_export_defaults.js
+++ b/src/legacy/ui/ui_exports/ui_export_defaults.js
@@ -24,6 +24,7 @@ export const UI_EXPORT_DEFAULTS = {
webpackNoParseRules: [
/node_modules[\/\\](angular|elasticsearch-browser)[\/\\]/,
/node_modules[\/\\](mocha|moment)[\/\\]/,
+ /node_modules[\/\\]vega-lib[\/\\]build[\/\\]vega\.js$/,
],
webpackAliases: {
diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs
index 1093153edbbf7..8a71c6ccb1506 100644
--- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs
+++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs
@@ -1,6 +1,7 @@
var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data'));
window.__kbnStrictCsp__ = kbnCsp.strictCsp;
window.__kbnDarkMode__ = {{darkMode}};
+window.__kbnPublicPath__ = {{publicPathMap}};
if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) {
var legacyBrowserError = document.getElementById('kbn_legacy_browser_error');
@@ -69,26 +70,16 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) {
}
load([
- {{#each sharedJsDepFilenames}}
- '{{../regularBundlePath}}/kbn-ui-shared-deps/{{this}}',
- {{/each}}
- '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}',
- '{{dllBundlePath}}/vendors_runtime.bundle.dll.js',
- {{#each dllJsChunks}}
+ {{#each jsDependencyPaths}}
'{{this}}',
{{/each}}
- '{{regularBundlePath}}/commons.bundle.js',
- {{!-- '{{regularBundlePath}}/plugin/data/data.plugin.js', --}}
- '{{regularBundlePath}}/plugin/kibanaUtils/kibanaUtils.plugin.js',
- '{{regularBundlePath}}/plugin/esUiShared/esUiShared.plugin.js',
- '{{regularBundlePath}}/plugin/kibanaReact/kibanaReact.plugin.js'
], function () {
load([
- '{{regularBundlePath}}/{{appId}}.bundle.js',
+ '{{entryBundlePath}}',
{{#each styleSheetPaths}}
'{{this}}',
{{/each}}
- ])
+ ]);
});
- };
+ }
}
diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js
index 0912d8683fc48..801eecf5b608b 100644
--- a/src/legacy/ui/ui_render/ui_render_mixin.js
+++ b/src/legacy/ui/ui_render/ui_render_mixin.js
@@ -103,41 +103,78 @@ export function uiRenderMixin(kbnServer, server, config) {
const dllJsChunks = DllCompiler.getRawDllConfig().chunks.map(
chunk => `${dllBundlePath}/vendors${chunk}.bundle.dll.js`
);
+
const styleSheetPaths = [
- ...dllStyleChunks,
+ ...(isCore ? [] : dllStyleChunks),
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
...(darkMode
? [
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`,
`${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`,
+ `${regularBundlePath}/dark_theme.style.css`,
]
: [
`${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`,
`${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`,
+ `${regularBundlePath}/light_theme.style.css`,
]),
- `${regularBundlePath}/${darkMode ? 'dark' : 'light'}_theme.style.css`,
`${regularBundlePath}/commons.style.css`,
- ...(!isCore ? [`${regularBundlePath}/${app.getId()}.style.css`] : []),
- ...kbnServer.uiExports.styleSheetPaths
- .filter(path => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light'))
- .map(path =>
- path.localPath.endsWith('.scss')
- ? `${basePath}/built_assets/css/${path.publicPath}`
- : `${basePath}/${path.publicPath}`
- )
- .reverse(),
+ ...(isCore
+ ? []
+ : [
+ `${regularBundlePath}/${app.getId()}.style.css`,
+ ...kbnServer.uiExports.styleSheetPaths
+ .filter(
+ path => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light')
+ )
+ .map(path =>
+ path.localPath.endsWith('.scss')
+ ? `${basePath}/built_assets/css/${path.publicPath}`
+ : `${basePath}/${path.publicPath}`
+ )
+ .reverse(),
+ ]),
];
+ const jsDependencyPaths = [
+ ...UiSharedDeps.jsDepFilenames.map(
+ filename => `${regularBundlePath}/kbn-ui-shared-deps/${filename}`
+ ),
+ `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`,
+ ...(isCore
+ ? []
+ : [
+ `${dllBundlePath}/vendors_runtime.bundle.dll.js`,
+ ...dllJsChunks,
+ `${regularBundlePath}/commons.bundle.js`,
+ ]),
+ `${regularBundlePath}/plugin/kibanaUtils/kibanaUtils.plugin.js`,
+ `${regularBundlePath}/plugin/esUiShared/esUiShared.plugin.js`,
+ `${regularBundlePath}/plugin/kibanaReact/kibanaReact.plugin.js`,
+ ];
+
+ const uiPluginIds = [...kbnServer.newPlatform.__internals.uiPlugins.public.keys()];
+
+ // These paths should align with the bundle routes configured in
+ // src/optimize/bundles_route/bundles_route.js
+ const publicPathMap = JSON.stringify({
+ core: `${regularBundlePath}/core/`,
+ 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`,
+ ...uiPluginIds.reduce(
+ (acc, pluginId) => ({ ...acc, [pluginId]: `${regularBundlePath}/plugin/${pluginId}/` }),
+ {}
+ ),
+ });
+
const bootstrap = new AppBootstrap({
templateData: {
- appId: isCore ? 'core' : app.getId(),
- regularBundlePath,
- dllBundlePath,
- dllJsChunks,
- styleSheetPaths,
- sharedJsFilename: UiSharedDeps.jsFilename,
- sharedJsDepFilenames: UiSharedDeps.jsDepFilenames,
darkMode,
+ jsDependencyPaths,
+ styleSheetPaths,
+ publicPathMap,
+ entryBundlePath: isCore
+ ? `${regularBundlePath}/core/core.entry.js`
+ : `${regularBundlePath}/${app.getId()}.bundle.js`,
},
});
diff --git a/src/optimize/bundles_route/bundles_route.js b/src/optimize/bundles_route/bundles_route.js
index 0c2e98b5acd63..4030988c8552c 100644
--- a/src/optimize/bundles_route/bundles_route.js
+++ b/src/optimize/bundles_route/bundles_route.js
@@ -17,11 +17,12 @@
* under the License.
*/
-import { isAbsolute, extname } from 'path';
+import { isAbsolute, extname, join } from 'path';
import LruCache from 'lru-cache';
import * as UiSharedDeps from '@kbn/ui-shared-deps';
import { createDynamicAssetResponse } from './dynamic_asset_response';
import { assertIsNpUiPluginPublicDirs } from '../np_ui_plugin_public_dirs';
+import { fromRoot } from '../../core/server/utils';
/**
* Creates the routes that serves files from `bundlesPath` or from
@@ -71,37 +72,57 @@ export function createBundlesRoute({
}
return [
- buildRouteForBundles(
- `${basePublicPath}/bundles/kbn-ui-shared-deps/`,
- '/bundles/kbn-ui-shared-deps/',
- UiSharedDeps.distDir,
- fileHashCache
- ),
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/bundles/kbn-ui-shared-deps/`,
+ routePath: '/bundles/kbn-ui-shared-deps/',
+ bundlesPath: UiSharedDeps.distDir,
+ fileHashCache,
+ replacePublicPath: false,
+ }),
...npUiPluginPublicDirs.map(({ id, path }) =>
- buildRouteForBundles(
- `${basePublicPath}/bundles/plugin/${id}/`,
- `/bundles/plugin/${id}/`,
- path,
- fileHashCache
- )
- ),
- buildRouteForBundles(
- `${basePublicPath}/bundles/`,
- '/bundles/',
- regularBundlesPath,
- fileHashCache
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/bundles/plugin/${id}/`,
+ routePath: `/bundles/plugin/${id}/`,
+ bundlesPath: path,
+ fileHashCache,
+ replacePublicPath: false,
+ })
),
- buildRouteForBundles(
- `${basePublicPath}/built_assets/dlls/`,
- '/built_assets/dlls/',
- dllBundlesPath,
- fileHashCache
- ),
- buildRouteForBundles(`${basePublicPath}/`, '/built_assets/css/', builtCssPath, fileHashCache),
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/bundles/core/`,
+ routePath: `/bundles/core/`,
+ bundlesPath: fromRoot(join('src', 'core', 'target', 'public')),
+ fileHashCache,
+ replacePublicPath: false,
+ }),
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/bundles/`,
+ routePath: '/bundles/',
+ bundlesPath: regularBundlesPath,
+ fileHashCache,
+ }),
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/built_assets/dlls/`,
+ routePath: '/built_assets/dlls/',
+ bundlesPath: dllBundlesPath,
+ fileHashCache,
+ }),
+ buildRouteForBundles({
+ publicPath: `${basePublicPath}/`,
+ routePath: '/built_assets/css/',
+ bundlesPath: builtCssPath,
+ fileHashCache,
+ }),
];
}
-function buildRouteForBundles(publicPath, routePath, bundlesPath, fileHashCache) {
+function buildRouteForBundles({
+ publicPath,
+ routePath,
+ bundlesPath,
+ fileHashCache,
+ replacePublicPath = true,
+}) {
return {
method: 'GET',
path: `${routePath}{path*}`,
@@ -122,6 +143,7 @@ function buildRouteForBundles(publicPath, routePath, bundlesPath, fileHashCache)
bundlesPath,
fileHashCache,
publicPath,
+ replacePublicPath,
});
},
},
diff --git a/src/optimize/bundles_route/dynamic_asset_response.js b/src/optimize/bundles_route/dynamic_asset_response.js
index 7af780a79e430..80c49a26270fd 100644
--- a/src/optimize/bundles_route/dynamic_asset_response.js
+++ b/src/optimize/bundles_route/dynamic_asset_response.js
@@ -52,7 +52,7 @@ import { replacePlaceholder } from '../public_path_placeholder';
* @property {LruCache} options.fileHashCache
*/
export async function createDynamicAssetResponse(options) {
- const { request, h, bundlesPath, publicPath, fileHashCache } = options;
+ const { request, h, bundlesPath, publicPath, fileHashCache, replacePublicPath } = options;
let fd;
try {
@@ -78,11 +78,14 @@ export async function createDynamicAssetResponse(options) {
});
fd = null; // read stream is now responsible for fd
+ const content = replacePublicPath ? replacePlaceholder(read, publicPath) : read;
+ const etag = replacePublicPath ? `${hash}-${publicPath}` : hash;
+
return h
- .response(replacePlaceholder(read, publicPath))
+ .response(content)
.takeover()
.code(200)
- .etag(`${hash}-${publicPath}`)
+ .etag(etag)
.header('cache-control', 'must-revalidate')
.type(request.server.mime.path(path).type);
} catch (error) {
diff --git a/src/plugins/dashboard/common/bwc/types.ts b/src/plugins/dashboard/common/bwc/types.ts
new file mode 100644
index 0000000000000..2427799345463
--- /dev/null
+++ b/src/plugins/dashboard/common/bwc/types.ts
@@ -0,0 +1,149 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectReference } from 'kibana/public';
+
+import { GridData } from '../';
+
+interface SavedObjectAttributes {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: string;
+ };
+}
+
+interface Doc {
+ references: SavedObjectReference[];
+ attributes: Attributes;
+ id: string;
+ type: string;
+}
+
+interface DocPre700 {
+ attributes: Attributes;
+ id: string;
+ type: string;
+}
+
+interface DashboardAttributes extends SavedObjectAttributes {
+ panelsJSON: string;
+ description: string;
+ version: number;
+ timeRestore: boolean;
+ useMargins?: boolean;
+ title: string;
+ optionsJSON?: string;
+}
+
+interface DashboardAttributesTo720 extends SavedObjectAttributes {
+ panelsJSON: string;
+ description: string;
+ uiStateJSON: string;
+ version: number;
+ timeRestore: boolean;
+ useMargins?: boolean;
+ title: string;
+ optionsJSON?: string;
+}
+
+export type DashboardDoc730ToLatest = Doc;
+
+export type DashboardDoc700To720 = Doc;
+
+export type DashboardDocPre700 = DocPre700;
+
+// Note that these types are prefixed with `Raw` because there are some post processing steps
+// that happen before the saved objects even reach the client. Namely, injecting type and id
+// parameters back into the panels, where the raw saved objects actually have them stored elsewhere.
+//
+// Ideally, everywhere in the dashboard code would use references at the top level instead of
+// embedded in the panels. The reason this is stored at the top level is so the references can be uniformly
+// updated across all saved object types that have references.
+
+// Starting in 7.3 we introduced the possibility of embeddables existing without an id
+// parameter. If there was no id, then type remains on the panel. So it either will have a name,
+// or a type property.
+export type RawSavedDashboardPanel730ToLatest = Pick<
+ RawSavedDashboardPanel640To720,
+ Exclude
+> & {
+ // Should be either type, and not name (not backed by a saved object), or name but not type (backed by a
+ // saved object and type and id are stored on references). Had trouble with oring the two types
+ // because of optional properties being marked as required: https://github.com/microsoft/TypeScript/issues/20722
+ readonly type?: string;
+ readonly name?: string;
+
+ panelIndex: string;
+};
+
+// NOTE!!
+// All of these types can actually exist in 7.2! The names are pretty confusing because we did
+// in place migrations for so long. For example, `RawSavedDashboardPanelTo60` is what a panel
+// created in 6.0 will look like after it's been migrated up to 7.2, *not* what it would look like in 6.0.
+// That's why it actually doesn't have id or type, but has a name property, because that was a migration
+// added in 7.0.
+
+// Hopefully since we finally have a formal saved object migration system and we can do less in place
+// migrations, this will be easier to understand moving forward.
+
+// Starting in 6.4 we added an in-place edit on panels to remove columns and sort properties and put them
+// inside the embeddable config (https://github.com/elastic/kibana/pull/17446).
+// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in
+// this shape in v 7.2.
+export type RawSavedDashboardPanel640To720 = Pick<
+ RawSavedDashboardPanel630,
+ Exclude
+>;
+
+// In 6.3.0 we expanded the number of grid columns and rows: https://github.com/elastic/kibana/pull/16763
+// We added in-place migrations to multiply older x,y,h,w numbers. Note the typescript shape here is the same
+// because it's just multiplying existing fields.
+// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in 7.2
+// that need to be modified.
+export type RawSavedDashboardPanel630 = RawSavedDashboardPanel620;
+
+// In 6.2 we added an inplace migration, moving uiState into each panel's new embeddableConfig property.
+// Source: https://github.com/elastic/kibana/pull/14949
+export type RawSavedDashboardPanel620 = RawSavedDashboardPanel610 & {
+ embeddableConfig: { [key: string]: unknown };
+ version: string;
+};
+
+// In 6.1 we switched from an angular grid to react grid layout (https://github.com/elastic/kibana/pull/13853)
+// This used gridData instead of size_x, size_y, row and col. We also started tracking the version this panel
+// was created in to make future migrations easier.
+// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in
+// this shape in v 7.2.
+export type RawSavedDashboardPanel610 = Pick<
+ RawSavedDashboardPanelTo60,
+ Exclude
+> & { gridData: GridData; version: string };
+
+export interface RawSavedDashboardPanelTo60 {
+ readonly columns?: string[];
+ readonly sort?: string;
+ readonly size_x?: number;
+ readonly size_y?: number;
+ readonly row: number;
+ readonly col: number;
+ panelIndex?: number | string; // earlier versions allowed this to be number or string. Some very early versions seem to be missing this entirely
+ readonly name: string;
+
+ // This is where custom panel titles are stored prior to Embeddable API v2
+ title?: string;
+}
diff --git a/src/plugins/dashboard/common/embeddable/types.ts b/src/plugins/dashboard/common/embeddable/types.ts
new file mode 100644
index 0000000000000..eb76d73af7a58
--- /dev/null
+++ b/src/plugins/dashboard/common/embeddable/types.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export interface GridData {
+ w: number;
+ h: number;
+ x: number;
+ y: number;
+ i: string;
+}
diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts
new file mode 100644
index 0000000000000..e3f3f629ae5d0
--- /dev/null
+++ b/src/plugins/dashboard/common/index.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { GridData } from './embeddable/types';
+export {
+ RawSavedDashboardPanel730ToLatest,
+ DashboardDoc730ToLatest,
+ DashboardDoc700To720,
+ DashboardDocPre700,
+} from './bwc/types';
+export {
+ SavedDashboardPanelTo60,
+ SavedDashboardPanel610,
+ SavedDashboardPanel620,
+ SavedDashboardPanel630,
+ SavedDashboardPanel640To720,
+ SavedDashboardPanel730ToLatest,
+} from './types';
+
+export { migratePanelsTo730 } from './migrate_to_730_panels';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts
similarity index 97%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts
rename to src/plugins/dashboard/common/migrate_to_730_panels.test.ts
index 4dd71fd8ee5f4..0867909225ddb 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts
+++ b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts
@@ -19,15 +19,12 @@
import { migratePanelsTo730 } from './migrate_to_730_panels';
import {
RawSavedDashboardPanelTo60,
- RawSavedDashboardPanel610,
- RawSavedDashboardPanel620,
RawSavedDashboardPanel630,
RawSavedDashboardPanel640To720,
- DEFAULT_PANEL_WIDTH,
- DEFAULT_PANEL_HEIGHT,
- SavedDashboardPanelTo60,
- SavedDashboardPanel730ToLatest,
-} from '../../../../../../plugins/dashboard/public';
+ RawSavedDashboardPanel610,
+ RawSavedDashboardPanel620,
+} from './bwc/types';
+import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from './types';
test('6.0 migrates uiState, sort, scales, and gridData', async () => {
const uiState = {
@@ -96,8 +93,8 @@ test('6.0 migration gives default width and height when missing', () => {
},
];
const newPanels = migratePanelsTo730(panels, '8.0.0', true);
- expect(newPanels[0].gridData.w).toBe(DEFAULT_PANEL_WIDTH);
- expect(newPanels[0].gridData.h).toBe(DEFAULT_PANEL_HEIGHT);
+ expect(newPanels[0].gridData.w).toBe(24);
+ expect(newPanels[0].gridData.h).toBe(15);
expect(newPanels[0].version).toBe('8.0.0');
});
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/plugins/dashboard/common/migrate_to_730_panels.ts
similarity index 97%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts
rename to src/plugins/dashboard/common/migrate_to_730_panels.ts
index a19c861f092d5..b89345f0a872c 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts
+++ b/src/plugins/dashboard/common/migrate_to_730_panels.ts
@@ -21,17 +21,19 @@ import semver from 'semver';
import uuid from 'uuid';
import {
GridData,
+ SavedDashboardPanelTo60,
+ SavedDashboardPanel620,
+ SavedDashboardPanel630,
+ SavedDashboardPanel610,
+} from './';
+import {
RawSavedDashboardPanelTo60,
RawSavedDashboardPanel630,
RawSavedDashboardPanel640To720,
RawSavedDashboardPanel730ToLatest,
RawSavedDashboardPanel610,
RawSavedDashboardPanel620,
- SavedDashboardPanelTo60,
- SavedDashboardPanel620,
- SavedDashboardPanel630,
- SavedDashboardPanel610,
-} from '../../../../../../plugins/dashboard/public';
+} from './bwc/types';
const PANEL_HEIGHT_SCALE_FACTOR = 5;
const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4;
@@ -92,7 +94,7 @@ function migratePre61PanelToLatest(
): RawSavedDashboardPanel730ToLatest {
if (panel.col === undefined || panel.row === undefined) {
throw new Error(
- i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', {
+ i18n.translate('dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', {
defaultMessage:
'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected col and/or row fields',
})
@@ -151,7 +153,7 @@ function migrate610PanelToLatest(
(['w', 'x', 'h', 'y'] as Array).forEach(key => {
if (panel.gridData[key] === undefined) {
throw new Error(
- i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', {
+ i18n.translate('dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', {
defaultMessage:
'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}',
values: { key },
diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts
new file mode 100644
index 0000000000000..7cc82a9173976
--- /dev/null
+++ b/src/plugins/dashboard/common/types.ts
@@ -0,0 +1,76 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ RawSavedDashboardPanelTo60,
+ RawSavedDashboardPanel610,
+ RawSavedDashboardPanel620,
+ RawSavedDashboardPanel630,
+ RawSavedDashboardPanel640To720,
+ RawSavedDashboardPanel730ToLatest,
+} from './bwc/types';
+
+export type SavedDashboardPanel640To720 = Pick<
+ RawSavedDashboardPanel640To720,
+ Exclude
+> & {
+ readonly id: string;
+ readonly type: string;
+};
+
+export type SavedDashboardPanel630 = Pick<
+ RawSavedDashboardPanel630,
+ Exclude
+> & {
+ readonly id: string;
+ readonly type: string;
+};
+
+export type SavedDashboardPanel620 = Pick<
+ RawSavedDashboardPanel620,
+ Exclude
+> & {
+ readonly id: string;
+ readonly type: string;
+};
+
+export type SavedDashboardPanel610 = Pick<
+ RawSavedDashboardPanel610,
+ Exclude
+> & {
+ readonly id: string;
+ readonly type: string;
+};
+
+export type SavedDashboardPanelTo60 = Pick<
+ RawSavedDashboardPanelTo60,
+ Exclude
+> & {
+ readonly id: string;
+ readonly type: string;
+};
+
+// id becomes optional starting in 7.3.0
+export type SavedDashboardPanel730ToLatest = Pick<
+ RawSavedDashboardPanel730ToLatest,
+ Exclude
+> & {
+ readonly id?: string;
+ readonly type: string;
+};
diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json
index 9bcd999c2dcc0..4cd8f3c7d981f 100644
--- a/src/plugins/dashboard/kibana.json
+++ b/src/plugins/dashboard/kibana.json
@@ -11,6 +11,6 @@
"savedObjects"
],
"optionalPlugins": ["home", "share", "usageCollection"],
- "server": false,
+ "server": true,
"ui": true
}
diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
index 7210879c5eacc..1bc85fa110ca0 100644
--- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -304,13 +304,13 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
url="/plugins/kibana/home/assets/welcome_graphic_light_2x.png"
>
@@ -998,13 +998,13 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`]
url="/plugins/kibana/home/assets/welcome_graphic_light_2x.png"
>
diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts
index 3134a5bfe2c67..a1696298117b0 100644
--- a/src/plugins/dashboard/public/application/application.ts
+++ b/src/plugins/dashboard/public/application/application.ts
@@ -38,12 +38,7 @@ import { EmbeddableStart } from '../../../embeddable/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../../navigation/public';
import { DataPublicPluginStart } from '../../../data/public';
import { SharePluginStart } from '../../../share/public';
-import {
- KibanaLegacyStart,
- configureAppAngularModule,
- createTopNavDirective,
- createTopNavHelper,
-} from '../../../kibana_legacy/public';
+import { KibanaLegacyStart, configureAppAngularModule } from '../../../kibana_legacy/public';
import { SavedObjectLoader } from '../../../saved_objects/public';
export interface RenderDeps {
@@ -114,13 +109,11 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) {
function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) {
createLocalI18nModule();
- createLocalTopNavModule(navigation);
createLocalIconModule();
const dashboardAngularModule = angular.module(moduleName, [
...thirdPartyAngularDependencies,
'app/dashboard/I18n',
- 'app/dashboard/TopNav',
'app/dashboard/icon',
]);
return dashboardAngularModule;
@@ -132,13 +125,6 @@ function createLocalIconModule() {
.directive('icon', reactDirective => reactDirective(EuiIcon));
}
-function createLocalTopNavModule(navigation: NavigationStart) {
- angular
- .module('app/dashboard/TopNav', ['react'])
- .directive('kbnTopNav', createTopNavDirective)
- .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui));
-}
-
function createLocalI18nModule() {
angular
.module('app/dashboard/I18n', [])
diff --git a/src/plugins/dashboard/public/application/dashboard_app.html b/src/plugins/dashboard/public/application/dashboard_app.html
index 3cf8932958b6d..87a5728ac2059 100644
--- a/src/plugins/dashboard/public/application/dashboard_app.html
+++ b/src/plugins/dashboard/public/application/dashboard_app.html
@@ -2,52 +2,7 @@
class="app-container dshAppContainer"
ng-class="{'dshAppContainer--withMargins': model.useMargins}"
>
-
-
-
-
-
-
-
-
+
{{screenTitle}}
diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx
index 150cd8f8fcbb5..f101935b9288d 100644
--- a/src/plugins/dashboard/public/application/dashboard_app.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app.tsx
@@ -33,7 +33,6 @@ import { SavedObjectDashboard } from '../saved_dashboards';
export interface DashboardAppScope extends ng.IScope {
dash: SavedObjectDashboard;
appState: DashboardAppState;
- screenTitle: string;
model: {
query: Query;
filters: Filter[];
@@ -54,21 +53,7 @@ export interface DashboardAppScope extends ng.IScope {
getShouldShowEditHelp: () => boolean;
getShouldShowViewHelp: () => boolean;
updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void;
- onRefreshChange: ({
- isPaused,
- refreshInterval,
- }: {
- isPaused: boolean;
- refreshInterval: any;
- }) => void;
- onFiltersUpdated: (filters: Filter[]) => void;
- onCancelApplyFilters: () => void;
- onApplyFilters: (filters: Filter[]) => void;
- onQuerySaved: (savedQuery: SavedQuery) => void;
- onSavedQueryUpdated: (savedQuery: SavedQuery) => void;
- onClearSavedQuery: () => void;
topNavMenu: any;
- showFilterBar: () => boolean;
showAddPanel: any;
showSaveQuery: boolean;
kbnTopNav: any;
diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
index 283fe9f0a83a4..fa2f06bfcdcdd 100644
--- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
@@ -21,12 +21,15 @@ import _, { uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui';
import React from 'react';
+import ReactDOM from 'react-dom';
import angular from 'angular';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { History } from 'history';
import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
+import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
+import { TimeRange } from 'src/plugins/data/public';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
import {
@@ -87,6 +90,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps {
dashboardConfig: KibanaLegacyStart['dashboardConfig'];
history: History;
kbnUrlStateStorage: IKbnUrlStateStorage;
+ navigation: NavigationStart;
}
export class DashboardAppController {
@@ -123,10 +127,13 @@ export class DashboardAppController {
history,
kbnUrlStateStorage,
usageCollection,
+ navigation,
}: DashboardAppControllerDependencies) {
const filterManager = queryService.filterManager;
const queryFilter = filterManager;
const timefilter = queryService.timefilter.timefilter;
+ let showSearchBar = true;
+ let showQueryBar = true;
let lastReloadRequestTime = 0;
const dash = ($scope.dash = $route.current.locals.dash);
@@ -243,6 +250,9 @@ export class DashboardAppController {
}
};
+ const showFilterBar = () =>
+ $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode();
+
const getEmptyScreenProps = (
shouldShowEditHelp: boolean,
isEmptyInReadOnlyMode: boolean
@@ -310,7 +320,6 @@ export class DashboardAppController {
refreshInterval: timefilter.getRefreshInterval(),
};
$scope.panels = dashboardStateManager.getPanels();
- $scope.screenTitle = dashboardStateManager.getTitle();
};
updateState();
@@ -515,49 +524,8 @@ export class DashboardAppController {
}
};
- $scope.onRefreshChange = function({ isPaused, refreshInterval }) {
- timefilter.setRefreshInterval({
- pause: isPaused,
- value: refreshInterval ? refreshInterval : $scope.model.refreshInterval.value,
- });
- };
-
- $scope.onFiltersUpdated = filters => {
- // The filters will automatically be set when the queryFilter emits an update event (see below)
- queryFilter.setFilters(filters);
- };
-
- $scope.onQuerySaved = savedQuery => {
- $scope.savedQuery = savedQuery;
- };
-
- $scope.onSavedQueryUpdated = savedQuery => {
- $scope.savedQuery = { ...savedQuery };
- };
-
- $scope.onClearSavedQuery = () => {
- delete $scope.savedQuery;
- dashboardStateManager.setSavedQueryId(undefined);
- dashboardStateManager.applyFilters(
- {
- query: '',
- language:
- localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'),
- },
- queryFilter.getGlobalFilters()
- );
- // Making this method sync broke the updates.
- // Temporary fix, until we fix the complex state in this file.
- setTimeout(() => {
- queryFilter.setFilters(queryFilter.getGlobalFilters());
- }, 0);
- };
-
const updateStateFromSavedQuery = (savedQuery: SavedQuery) => {
- const savedQueryFilters = savedQuery.attributes.filters || [];
- const globalFilters = queryFilter.getGlobalFilters();
- const allFilters = [...globalFilters, ...savedQueryFilters];
-
+ const allFilters = filterManager.getFilters();
dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters);
if (savedQuery.attributes.timefilter) {
timefilter.setTime({
@@ -616,6 +584,48 @@ export class DashboardAppController {
}
);
+ const onSavedQueryIdChange = (savedQueryId?: string) => {
+ dashboardStateManager.setSavedQueryId(savedQueryId);
+ };
+
+ const getNavBarProps = () => {
+ const isFullScreenMode = dashboardStateManager.getFullScreenMode();
+ const screenTitle = dashboardStateManager.getTitle();
+ return {
+ appName: 'dashboard',
+ config: $scope.isVisible ? $scope.topNavMenu : undefined,
+ className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined,
+ screenTitle,
+ showSearchBar,
+ showQueryBar,
+ showFilterBar: showFilterBar(),
+ indexPatterns: $scope.indexPatterns,
+ showSaveQuery: $scope.showSaveQuery,
+ query: $scope.model.query,
+ savedQuery: $scope.savedQuery,
+ onSavedQueryIdChange,
+ savedQueryId: dashboardStateManager.getSavedQueryId(),
+ useDefaultBehaviors: true,
+ onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => {
+ if (!payload.query) {
+ $scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange });
+ } else {
+ $scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange });
+ }
+ },
+ };
+ };
+ const dashboardNavBar = document.getElementById('dashboardChrome');
+ const updateNavBar = () => {
+ ReactDOM.render(, dashboardNavBar);
+ };
+
+ const unmountNavBar = () => {
+ if (dashboardNavBar) {
+ ReactDOM.unmountComponentAtNode(dashboardNavBar);
+ }
+ };
+
$scope.timefilterSubscriptions$ = new Subscription();
$scope.timefilterSubscriptions$.add(
@@ -707,6 +717,8 @@ export class DashboardAppController {
revertChangesAndExitEditMode();
}
});
+
+ updateNavBar();
};
/**
@@ -761,9 +773,6 @@ export class DashboardAppController {
});
}
- $scope.showFilterBar = () =>
- $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode();
-
$scope.showAddPanel = () => {
dashboardStateManager.setFullScreenMode(false);
/*
@@ -785,7 +794,11 @@ export class DashboardAppController {
const navActions: {
[key: string]: NavAction;
} = {};
- navActions[TopNavIds.FULL_SCREEN] = () => dashboardStateManager.setFullScreenMode(true);
+ navActions[TopNavIds.FULL_SCREEN] = () => {
+ dashboardStateManager.setFullScreenMode(true);
+ showQueryBar = false;
+ updateNavBar();
+ };
navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW);
navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT);
navActions[TopNavIds.SAVE] = () => {
@@ -858,6 +871,7 @@ export class DashboardAppController {
if ((response as { error: Error }).error) {
dashboardStateManager.setTitle(currentTitle);
}
+ updateNavBar();
return response;
});
};
@@ -939,6 +953,9 @@ export class DashboardAppController {
const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => {
$scope.$evalAsync(() => {
$scope.isVisible = isVisible;
+ showSearchBar = isVisible || showFilterBar();
+ showQueryBar = !dashboardStateManager.getFullScreenMode() && isVisible;
+ updateNavBar();
});
});
@@ -949,9 +966,17 @@ export class DashboardAppController {
navActions,
dashboardConfig.getHideWriteControls()
);
+ updateNavBar();
+ });
+
+ $scope.$watch('indexPatterns', () => {
+ updateNavBar();
});
$scope.$on('$destroy', () => {
+ // we have to unmount nav bar manually to make sure all internal subscriptions are unsubscribed
+ unmountNavBar();
+
updateSubscription.unsubscribe();
stopSyncingQueryServiceStateWithUrl();
stopSyncingAppFilters();
diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
index b15a813aff903..fb33649093c8d 100644
--- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
+++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx
@@ -29,9 +29,10 @@ import _ from 'lodash';
import React from 'react';
import { Subscription } from 'rxjs';
import ReactGridLayout, { Layout } from 'react-grid-layout';
+import { GridData } from '../../../../common';
import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin';
import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants';
-import { DashboardPanelState, GridData } from '../types';
+import { DashboardPanelState } from '../types';
import { withKibana } from '../../../../../kibana_react/public';
import { DashboardContainerInput } from '../dashboard_container';
import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container';
diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
index 70a6c83418587..b95b7f394a27d 100644
--- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
+++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts
@@ -18,7 +18,8 @@
*/
import { PanelNotFoundError } from '../../../embeddable_plugin';
-import { DashboardPanelState, GridData, DASHBOARD_GRID_COLUMN_COUNT } from '..';
+import { GridData } from '../../../../common';
+import { DashboardPanelState, DASHBOARD_GRID_COLUMN_COUNT } from '..';
export type PanelPlacementMethod = (
args: PlacementArgs
diff --git a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
index 30a93989649a7..b3ce2f1e57d5f 100644
--- a/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
+++ b/src/plugins/dashboard/public/application/embeddable/placeholder/placeholder_embeddable_factory.ts
@@ -33,6 +33,10 @@ export class PlaceholderEmbeddableFactory implements EmbeddableFactoryDefinition
return false;
}
+ public canCreateNew() {
+ return false;
+ }
+
public async create(initialInput: EmbeddableInput, parent?: IContainer) {
return new PlaceholderEmbeddable(initialInput, parent);
}
diff --git a/src/plugins/dashboard/public/application/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts
index 6d0221cb10e8b..66cdd22ed6bd4 100644
--- a/src/plugins/dashboard/public/application/embeddable/types.ts
+++ b/src/plugins/dashboard/public/application/embeddable/types.ts
@@ -17,18 +17,11 @@
* under the License.
*/
import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public';
+import { GridData } from '../../../common';
import { PanelState, EmbeddableInput } from '../../embeddable_plugin';
export type PanelId = string;
export type SavedObjectId = string;
-export interface GridData {
- w: number;
- h: number;
- x: number;
- y: number;
- i: string;
-}
-
export interface DashboardPanelState<
TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
> extends PanelState {
diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js
index 10243dbf2f979..31225530b10b9 100644
--- a/src/plugins/dashboard/public/application/legacy_app.js
+++ b/src/plugins/dashboard/public/application/legacy_app.js
@@ -28,7 +28,6 @@ import { initDashboardAppDirective } from './dashboard_app';
import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants';
import {
createKbnUrlStateStorage,
- ensureDefaultIndexPattern,
redirectWhenMissing,
InvalidJSONProperty,
SavedObjectNotFound,
@@ -138,7 +137,7 @@ export function initDashboardApp(app, deps) {
},
resolve: {
dash: function($route, history) {
- return ensureDefaultIndexPattern(deps.core, deps.data, history).then(() => {
+ return deps.data.indexPatterns.ensureDefaultIndexPattern(history).then(() => {
const savedObjectsClient = deps.savedObjectsClient;
const title = $route.current.params.title;
if (title) {
@@ -173,7 +172,8 @@ export function initDashboardApp(app, deps) {
requireUICapability: 'dashboard.createNew',
resolve: {
dash: history =>
- ensureDefaultIndexPattern(deps.core, deps.data, history)
+ deps.data.indexPatterns
+ .ensureDefaultIndexPattern(history)
.then(() => deps.savedDashboards.get())
.catch(
redirectWhenMissing({
@@ -194,7 +194,8 @@ export function initDashboardApp(app, deps) {
dash: function($route, history) {
const id = $route.current.params.id;
- return ensureDefaultIndexPattern(deps.core, deps.data, history)
+ return deps.data.indexPatterns
+ .ensureDefaultIndexPattern(history)
.then(() => deps.savedDashboards.get(id))
.then(savedDashboard => {
deps.chrome.recentlyAccessed.add(
diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts
index 8f8de3663518a..f4d97578adebf 100644
--- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts
+++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts
@@ -22,18 +22,16 @@ import { i18n } from '@kbn/i18n';
import { METRIC_TYPE } from '@kbn/analytics';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { DashboardAppState, SavedDashboardPanel } from '../../types';
import {
- DashboardAppState,
+ migratePanelsTo730,
SavedDashboardPanelTo60,
SavedDashboardPanel730ToLatest,
SavedDashboardPanel610,
SavedDashboardPanel630,
SavedDashboardPanel640To720,
SavedDashboardPanel620,
- SavedDashboardPanel,
-} from '../../types';
-// should be moved in src/plugins/dashboard/common right after https://github.com/elastic/kibana/pull/61895 is merged
-import { migratePanelsTo730 } from '../../../../../legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels';
+} from '../../../common';
/**
* Attempts to migrate the state stored in the URL into the latest version of it.
diff --git a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts
index 57c147ffe3588..ee59c68cce451 100644
--- a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts
+++ b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts
@@ -17,17 +17,19 @@
* under the License.
*/
-import { searchSourceMock } from '../../../../data/public/mocks';
+import { dataPluginMock } from '../../../../data/public/mocks';
import { SavedObjectDashboard } from '../../saved_dashboards';
export function getSavedDashboardMock(
config?: Partial
): SavedObjectDashboard {
+ const searchSource = dataPluginMock.createStartContract();
+
return {
id: '123',
title: 'my dashboard',
panelsJSON: '[]',
- searchSource: searchSourceMock,
+ searchSource: searchSource.search.searchSource.create(),
copyOnSave: false,
timeRestore: false,
timeTo: 'now',
diff --git a/src/plugins/dashboard/public/bwc/index.ts b/src/plugins/dashboard/public/bwc/index.ts
deleted file mode 100644
index d8f7b5091eb8f..0000000000000
--- a/src/plugins/dashboard/public/bwc/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export * from './types';
diff --git a/src/plugins/dashboard/public/bwc/types.ts b/src/plugins/dashboard/public/bwc/types.ts
deleted file mode 100644
index d5655e525e9bd..0000000000000
--- a/src/plugins/dashboard/public/bwc/types.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { SavedObjectReference } from 'kibana/public';
-import { GridData } from '../application';
-
-export interface SavedObjectAttributes {
- kibanaSavedObjectMeta: {
- searchSourceJSON: string;
- };
-}
-
-export interface Doc {
- references: SavedObjectReference[];
- attributes: Attributes;
- id: string;
- type: string;
-}
-
-export interface DocPre700 {
- attributes: Attributes;
- id: string;
- type: string;
-}
-
-export interface SavedObjectAttributes {
- kibanaSavedObjectMeta: {
- searchSourceJSON: string;
- };
-}
-
-interface DashboardAttributes extends SavedObjectAttributes {
- panelsJSON: string;
- description: string;
- version: number;
- timeRestore: boolean;
- useMargins?: boolean;
- title: string;
- optionsJSON?: string;
-}
-
-export type DashboardAttributes730ToLatest = DashboardAttributes;
-
-interface DashboardAttributesTo720 extends SavedObjectAttributes {
- panelsJSON: string;
- description: string;
- uiStateJSON: string;
- version: number;
- timeRestore: boolean;
- useMargins?: boolean;
- title: string;
- optionsJSON?: string;
-}
-
-export type DashboardDoc730ToLatest = Doc;
-
-export type DashboardDoc700To720 = Doc;
-
-export type DashboardDocPre700 = DocPre700;
-
-// Note that these types are prefixed with `Raw` because there are some post processing steps
-// that happen before the saved objects even reach the client. Namely, injecting type and id
-// parameters back into the panels, where the raw saved objects actually have them stored elsewhere.
-//
-// Ideally, everywhere in the dashboard code would use references at the top level instead of
-// embedded in the panels. The reason this is stored at the top level is so the references can be uniformly
-// updated across all saved object types that have references.
-
-// Starting in 7.3 we introduced the possibility of embeddables existing without an id
-// parameter. If there was no id, then type remains on the panel. So it either will have a name,
-// or a type property.
-export type RawSavedDashboardPanel730ToLatest = Pick<
- RawSavedDashboardPanel640To720,
- Exclude
-> & {
- // Should be either type, and not name (not backed by a saved object), or name but not type (backed by a
- // saved object and type and id are stored on references). Had trouble with oring the two types
- // because of optional properties being marked as required: https://github.com/microsoft/TypeScript/issues/20722
- readonly type?: string;
- readonly name?: string;
-
- panelIndex: string;
-};
-
-// NOTE!!
-// All of these types can actually exist in 7.2! The names are pretty confusing because we did
-// in place migrations for so long. For example, `RawSavedDashboardPanelTo60` is what a panel
-// created in 6.0 will look like after it's been migrated up to 7.2, *not* what it would look like in 6.0.
-// That's why it actually doesn't have id or type, but has a name property, because that was a migration
-// added in 7.0.
-
-// Hopefully since we finally have a formal saved object migration system and we can do less in place
-// migrations, this will be easier to understand moving forward.
-
-// Starting in 6.4 we added an in-place edit on panels to remove columns and sort properties and put them
-// inside the embeddable config (https://github.com/elastic/kibana/pull/17446).
-// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in
-// this shape in v 7.2.
-export type RawSavedDashboardPanel640To720 = Pick<
- RawSavedDashboardPanel630,
- Exclude
->;
-
-// In 6.3.0 we expanded the number of grid columns and rows: https://github.com/elastic/kibana/pull/16763
-// We added in-place migrations to multiply older x,y,h,w numbers. Note the typescript shape here is the same
-// because it's just multiplying existing fields.
-// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in 7.2
-// that need to be modified.
-export type RawSavedDashboardPanel630 = RawSavedDashboardPanel620;
-
-// In 6.2 we added an inplace migration, moving uiState into each panel's new embeddableConfig property.
-// Source: https://github.com/elastic/kibana/pull/14949
-export type RawSavedDashboardPanel620 = RawSavedDashboardPanel610 & {
- embeddableConfig: { [key: string]: unknown };
- version: string;
-};
-
-// In 6.1 we switched from an angular grid to react grid layout (https://github.com/elastic/kibana/pull/13853)
-// This used gridData instead of size_x, size_y, row and col. We also started tracking the version this panel
-// was created in to make future migrations easier.
-// Note that this was not added as a saved object migration until 7.3, so there can still exist panels in
-// this shape in v 7.2.
-export type RawSavedDashboardPanel610 = Pick<
- RawSavedDashboardPanelTo60,
- Exclude
-> & { gridData: GridData; version: string };
-
-export interface RawSavedDashboardPanelTo60 {
- readonly columns?: string[];
- readonly sort?: string;
- readonly size_x?: number;
- readonly size_y?: number;
- readonly row: number;
- readonly col: number;
- panelIndex?: number | string; // earlier versions allowed this to be number or string. Some very early versions seem to be missing this entirely
- readonly name: string;
-
- // This is where custom panel titles are stored prior to Embeddable API v2
- title?: string;
-}
diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts
index ca0ea0293b07c..44733499cdcba 100644
--- a/src/plugins/dashboard/public/index.ts
+++ b/src/plugins/dashboard/public/index.ts
@@ -20,29 +20,6 @@
import { PluginInitializerContext } from '../../../core/public';
import { DashboardPlugin } from './plugin';
-/**
- * These types can probably be internal once all of dashboard app is migrated into this plugin. Right
- * now, migrations are still in legacy land.
- */
-export {
- DashboardDoc730ToLatest,
- DashboardDoc700To720,
- RawSavedDashboardPanelTo60,
- RawSavedDashboardPanel610,
- RawSavedDashboardPanel620,
- RawSavedDashboardPanel630,
- RawSavedDashboardPanel640To720,
- RawSavedDashboardPanel730ToLatest,
- DashboardDocPre700,
-} from './bwc';
-export {
- SavedDashboardPanelTo60,
- SavedDashboardPanel610,
- SavedDashboardPanel620,
- SavedDashboardPanel630,
- SavedDashboardPanel730ToLatest,
-} from './types';
-
export {
DashboardContainer,
DashboardContainerInput,
@@ -51,7 +28,6 @@ export {
// Types below here can likely be made private when dashboard app moved into this NP plugin.
DEFAULT_PANEL_WIDTH,
DEFAULT_PANEL_HEIGHT,
- GridData,
} from './application';
export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 203c784d9df4e..5f6b67ee6ad20 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -251,6 +251,8 @@ export class DashboardPlugin
localStorage: new Storage(localStorage),
usageCollection,
};
+ // make sure the index pattern list is up to date
+ await dataStart.indexPatterns.clearCache();
const { renderApp } = await import('./application/application');
const unmount = renderApp(params.element, params.appBasePath, deps);
return () => {
diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts
index d96d2cdf75626..21c6bbc1bfc51 100644
--- a/src/plugins/dashboard/public/types.ts
+++ b/src/plugins/dashboard/public/types.ts
@@ -19,14 +19,7 @@
import { Query, Filter } from 'src/plugins/data/public';
import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public';
-import {
- RawSavedDashboardPanelTo60,
- RawSavedDashboardPanel610,
- RawSavedDashboardPanel620,
- RawSavedDashboardPanel630,
- RawSavedDashboardPanel640To720,
- RawSavedDashboardPanel730ToLatest,
-} from './bwc';
+import { SavedDashboardPanel730ToLatest } from '../common';
import { ViewMode } from './embeddable_plugin';
export interface DashboardCapabilities {
@@ -83,55 +76,6 @@ export type NavAction = (anchorElement?: any) => void;
*/
export type SavedDashboardPanel = SavedDashboardPanel730ToLatest;
-// id becomes optional starting in 7.3.0
-export type SavedDashboardPanel730ToLatest = Pick<
- RawSavedDashboardPanel730ToLatest,
- Exclude
-> & {
- readonly id?: string;
- readonly type: string;
-};
-
-export type SavedDashboardPanel640To720 = Pick<
- RawSavedDashboardPanel640To720,
- Exclude
-> & {
- readonly id: string;
- readonly type: string;
-};
-
-export type SavedDashboardPanel630 = Pick<
- RawSavedDashboardPanel630,
- Exclude
-> & {
- readonly id: string;
- readonly type: string;
-};
-
-export type SavedDashboardPanel620 = Pick<
- RawSavedDashboardPanel620,
- Exclude
-> & {
- readonly id: string;
- readonly type: string;
-};
-
-export type SavedDashboardPanel610 = Pick<
- RawSavedDashboardPanel610,
- Exclude
-> & {
- readonly id: string;
- readonly type: string;
-};
-
-export type SavedDashboardPanelTo60 = Pick<
- RawSavedDashboardPanelTo60,
- Exclude
-> & {
- readonly id: string;
- readonly type: string;
-};
-
export interface DashboardAppState {
panels: SavedDashboardPanel[];
fullScreenMode: boolean;
diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts
new file mode 100644
index 0000000000000..9719586001c59
--- /dev/null
+++ b/src/plugins/dashboard/server/index.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { PluginInitializerContext } from '../../../core/server';
+import { DashboardPlugin } from './plugin';
+
+// This exports static code and TypeScript types,
+// as well as, Kibana Platform `plugin()` initializer.
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new DashboardPlugin(initializerContext);
+}
+
+export { DashboardPluginSetup, DashboardPluginStart } from './types';
diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts
new file mode 100644
index 0000000000000..5d1b66002e749
--- /dev/null
+++ b/src/plugins/dashboard/server/plugin.ts
@@ -0,0 +1,53 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ PluginInitializerContext,
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ Logger,
+} from '../../../core/server';
+
+import { dashboardSavedObjectType } from './saved_objects';
+
+import { DashboardPluginSetup, DashboardPluginStart } from './types';
+
+export class DashboardPlugin implements Plugin {
+ private readonly logger: Logger;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.logger = initializerContext.logger.get();
+ }
+
+ public setup(core: CoreSetup) {
+ this.logger.debug('dashboard: Setup');
+
+ core.savedObjects.registerType(dashboardSavedObjectType);
+
+ return {};
+ }
+
+ public start(core: CoreStart) {
+ this.logger.debug('dashboard: Started');
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts
new file mode 100644
index 0000000000000..65d5a4021f962
--- /dev/null
+++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectsType } from 'kibana/server';
+import { dashboardSavedObjectTypeMigrations } from './dashboard_migrations';
+
+export const dashboardSavedObjectType: SavedObjectsType = {
+ name: 'dashboard',
+ hidden: false,
+ namespaceType: 'single',
+ management: {
+ icon: 'dashboardApp',
+ defaultSearchField: 'title',
+ importableAndExportable: true,
+ getTitle(obj) {
+ return obj.attributes.title;
+ },
+ getEditUrl(obj) {
+ return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`;
+ },
+ getInAppUrl(obj) {
+ return {
+ path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`,
+ uiCapabilitiesPath: 'dashboard.show',
+ };
+ },
+ },
+ mappings: {
+ properties: {
+ description: { type: 'text' },
+ hits: { type: 'integer' },
+ kibanaSavedObjectMeta: { properties: { searchSourceJSON: { type: 'text' } } },
+ optionsJSON: { type: 'text' },
+ panelsJSON: { type: 'text' },
+ refreshInterval: {
+ properties: {
+ display: { type: 'keyword' },
+ pause: { type: 'boolean' },
+ section: { type: 'integer' },
+ value: { type: 'integer' },
+ },
+ },
+ timeFrom: { type: 'keyword' },
+ timeRestore: { type: 'boolean' },
+ timeTo: { type: 'keyword' },
+ title: { type: 'text' },
+ version: { type: 'integer' },
+ },
+ },
+ migrations: dashboardSavedObjectTypeMigrations,
+};
diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts
new file mode 100644
index 0000000000000..9829498118cc0
--- /dev/null
+++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts
@@ -0,0 +1,448 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectUnsanitizedDoc } from 'kibana/server';
+import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations';
+
+describe('dashboard', () => {
+ describe('7.0.0', () => {
+ const migration = migrations['7.0.0'];
+
+ test('skips error on empty object', () => {
+ expect(migration({} as SavedObjectUnsanitizedDoc)).toMatchInlineSnapshot(`
+Object {
+ "references": Array [],
+}
+`);
+ });
+
+ test('skips errors when searchSourceJSON is null', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: null,
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ const migratedDoc = migration(doc);
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": null,
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('skips errors when searchSourceJSON is undefined', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: undefined,
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ const migratedDoc = migration(doc);
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": undefined,
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('skips error when searchSourceJSON is not a string', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: 123,
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": 123,
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('skips error when searchSourceJSON is invalid json', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: '{abc123}',
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": "{abc123}",
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('skips error when "index" and "filter" is missing from searchSourceJSON', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({ bar: true }),
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ const migratedDoc = migration(doc);
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": "{\\"bar\\":true}",
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('extracts "index" attribute from doc', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }),
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ const migratedDoc = migration(doc);
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}",
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "pattern*",
+ "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
+ "type": "index-pattern",
+ },
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('extracts index patterns from filter', () => {
+ const doc = {
+ id: '1',
+ type: 'dashboard',
+ attributes: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ bar: true,
+ filter: [
+ {
+ meta: {
+ foo: true,
+ index: 'my-index',
+ },
+ },
+ ],
+ }),
+ },
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ };
+ const migratedDoc = migration(doc);
+
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "kibanaSavedObjectMeta": Object {
+ "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}",
+ },
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "my-index",
+ "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index",
+ "type": "index-pattern",
+ },
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+ "type": "dashboard",
+}
+`);
+ });
+
+ test('skips error when panelsJSON is not a string', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON: 123,
+ },
+ } as SavedObjectUnsanitizedDoc;
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": 123,
+ },
+ "id": "1",
+ "references": Array [],
+}
+`);
+ });
+
+ test('skips error when panelsJSON is not valid JSON', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON: '{123abc}',
+ },
+ } as SavedObjectUnsanitizedDoc;
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": "{123abc}",
+ },
+ "id": "1",
+ "references": Array [],
+}
+`);
+ });
+
+ test('skips panelsJSON when its not an array', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON: '{}',
+ },
+ } as SavedObjectUnsanitizedDoc;
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": "{}",
+ },
+ "id": "1",
+ "references": Array [],
+}
+`);
+ });
+
+ test('skips error when a panel is missing "type" attribute', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON: '[{"id":"123"}]',
+ },
+ } as SavedObjectUnsanitizedDoc;
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": "[{\\"id\\":\\"123\\"}]",
+ },
+ "id": "1",
+ "references": Array [],
+}
+`);
+ });
+
+ test('skips error when a panel is missing "id" attribute', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON: '[{"type":"visualization"}]',
+ },
+ } as SavedObjectUnsanitizedDoc;
+ expect(migration(doc)).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": "[{\\"type\\":\\"visualization\\"}]",
+ },
+ "id": "1",
+ "references": Array [],
+}
+`);
+ });
+
+ test('extract panel references from doc', () => {
+ const doc = {
+ id: '1',
+ attributes: {
+ panelsJSON:
+ '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
+ },
+ } as SavedObjectUnsanitizedDoc;
+ const migratedDoc = migration(doc);
+ expect(migratedDoc).toMatchInlineSnapshot(`
+Object {
+ "attributes": Object {
+ "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]",
+ },
+ "id": "1",
+ "references": Array [
+ Object {
+ "id": "1",
+ "name": "panel_0",
+ "type": "visualization",
+ },
+ Object {
+ "id": "2",
+ "name": "panel_1",
+ "type": "visualization",
+ },
+ ],
+}
+`);
+ });
+ });
+});
diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts
new file mode 100644
index 0000000000000..7c1d0568cd3d7
--- /dev/null
+++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts
@@ -0,0 +1,117 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { get, flow } from 'lodash';
+
+import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server';
+import { migrations730 } from './migrations_730';
+import { migrateMatchAllQuery } from './migrate_match_all_query';
+import { DashboardDoc700To720 } from '../../common';
+
+function migrateIndexPattern(doc: DashboardDoc700To720) {
+ const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
+ if (typeof searchSourceJSON !== 'string') {
+ return;
+ }
+ let searchSource;
+ try {
+ searchSource = JSON.parse(searchSourceJSON);
+ } catch (e) {
+ // Let it go, the data is invalid and we'll leave it as is
+ return;
+ }
+ if (searchSource.index) {
+ searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index';
+ doc.references.push({
+ name: searchSource.indexRefName,
+ type: 'index-pattern',
+ id: searchSource.index,
+ });
+ delete searchSource.index;
+ }
+ if (searchSource.filter) {
+ searchSource.filter.forEach((filterRow: any, i: number) => {
+ if (!filterRow.meta || !filterRow.meta.index) {
+ return;
+ }
+ filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`;
+ doc.references.push({
+ name: filterRow.meta.indexRefName,
+ type: 'index-pattern',
+ id: filterRow.meta.index,
+ });
+ delete filterRow.meta.index;
+ });
+ }
+ doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
+}
+
+const migrations700: SavedObjectMigrationFn = (doc): DashboardDoc700To720 => {
+ // Set new "references" attribute
+ doc.references = doc.references || [];
+
+ // Migrate index pattern
+ migrateIndexPattern(doc as DashboardDoc700To720);
+ // Migrate panels
+ const panelsJSON = get(doc, 'attributes.panelsJSON');
+ if (typeof panelsJSON !== 'string') {
+ return doc as DashboardDoc700To720;
+ }
+ let panels;
+ try {
+ panels = JSON.parse(panelsJSON);
+ } catch (e) {
+ // Let it go, the data is invalid and we'll leave it as is
+ return doc as DashboardDoc700To720;
+ }
+ if (!Array.isArray(panels)) {
+ return doc as DashboardDoc700To720;
+ }
+ panels.forEach((panel, i) => {
+ if (!panel.type || !panel.id) {
+ return;
+ }
+ panel.panelRefName = `panel_${i}`;
+ doc.references!.push({
+ name: `panel_${i}`,
+ type: panel.type,
+ id: panel.id,
+ });
+ delete panel.type;
+ delete panel.id;
+ });
+ doc.attributes.panelsJSON = JSON.stringify(panels);
+ return doc as DashboardDoc700To720;
+};
+
+export const dashboardSavedObjectTypeMigrations = {
+ /**
+ * We need to have this migration twice, once with a version prior to 7.0.0 once with a version
+ * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already
+ * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running,
+ * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we
+ * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects
+ * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced
+ * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7
+ * only contained the 6.7.2 migration and not the 7.0.1 migration.
+ */
+ '6.7.2': flow(migrateMatchAllQuery),
+ '7.0.0': flow<(doc: SavedObjectUnsanitizedDoc) => DashboardDoc700To720>(migrations700),
+ '7.3.0': flow(migrations730),
+};
diff --git a/src/plugins/dashboard/server/saved_objects/index.ts b/src/plugins/dashboard/server/saved_objects/index.ts
new file mode 100644
index 0000000000000..ca97b9d2a6b70
--- /dev/null
+++ b/src/plugins/dashboard/server/saved_objects/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { dashboardSavedObjectType } from './dashboard';
diff --git a/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts
new file mode 100644
index 0000000000000..c9b35263a549f
--- /dev/null
+++ b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectUnsanitizedDoc } from 'kibana/server';
+import { DashboardDoc730ToLatest } from '../../common';
+
+function isDoc(
+ doc: { [key: string]: unknown } | SavedObjectUnsanitizedDoc
+): doc is SavedObjectUnsanitizedDoc {
+ return (
+ typeof doc.id === 'string' &&
+ typeof doc.type === 'string' &&
+ doc.attributes !== null &&
+ typeof doc.attributes === 'object' &&
+ doc.references !== null &&
+ typeof doc.references === 'object'
+ );
+}
+
+export function isDashboardDoc(
+ doc: { [key: string]: unknown } | DashboardDoc730ToLatest
+): doc is DashboardDoc730ToLatest {
+ if (!isDoc(doc)) {
+ return false;
+ }
+
+ if (typeof (doc as DashboardDoc730ToLatest).attributes.panelsJSON !== 'string') {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts
rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts
similarity index 95%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts
rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts
index 707aae9e5d4ac..5b8582bf821ef 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts
+++ b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts
@@ -19,7 +19,7 @@
import { SavedObjectMigrationFn } from 'kibana/server';
import { get } from 'lodash';
-import { DEFAULT_QUERY_LANGUAGE } from '../../../../../../plugins/data/common';
+import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common';
export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => {
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts
similarity index 91%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts
rename to src/plugins/dashboard/server/saved_objects/migrations_730.test.ts
index 5a4970897098d..aa744324428a4 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts
+++ b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts
@@ -17,20 +17,18 @@
* under the License.
*/
-import { migrations } from '../../../migrations';
+import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations';
import { migrations730 } from './migrations_730';
-import {
- DashboardDoc700To720,
- DashboardDoc730ToLatest,
- RawSavedDashboardPanel730ToLatest,
- DashboardDocPre700,
-} from '../../../../../../plugins/dashboard/public';
-
-const mockLogger = {
- warning: () => {},
- warn: () => {},
- debug: () => {},
- info: () => {},
+import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common';
+import { RawSavedDashboardPanel730ToLatest } from '../../common';
+
+const mockContext = {
+ log: {
+ warning: () => {},
+ warn: () => {},
+ debug: () => {},
+ info: () => {},
+ },
};
test('dashboard migration 7.3.0 migrates filters to query on search source', () => {
@@ -53,7 +51,7 @@ test('dashboard migration 7.3.0 migrates filters to query on search source', ()
'[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]',
},
};
- const newDoc = migrations730(doc, mockLogger);
+ const newDoc = migrations730(doc, mockContext);
expect(newDoc).toMatchInlineSnapshot(`
Object {
@@ -97,8 +95,8 @@ test('dashboard migration 7.3.0 migrates filters to query on search source when
},
};
- const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger);
- const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger);
+ const doc700: DashboardDoc700To720 = migrations['7.0.0'](doc);
+ const newDoc = migrations['7.3.0'](doc700, mockContext);
const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON);
expect(parsedSearchSource.filter.length).toBe(0);
@@ -129,8 +127,8 @@ test('dashboard migration works when panelsJSON is missing panelIndex', () => {
},
};
- const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger);
- const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger);
+ const doc700: DashboardDoc700To720 = migrations['7.0.0'](doc);
+ const newDoc = migrations['7.3.0'](doc700, mockContext);
const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON);
expect(parsedSearchSource.filter.length).toBe(0);
@@ -159,7 +157,7 @@ test('dashboard migration 7.3.0 migrates panels', () => {
},
};
- const newDoc = migrations730(doc, mockLogger) as DashboardDoc730ToLatest;
+ const newDoc = migrations730(doc, mockContext) as DashboardDoc730ToLatest;
const newPanels = JSON.parse(newDoc.attributes.panelsJSON) as RawSavedDashboardPanel730ToLatest[];
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.ts
similarity index 79%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts
rename to src/plugins/dashboard/server/saved_objects/migrations_730.ts
index 56856f7b21303..e9d483f68a5da 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts
+++ b/src/plugins/dashboard/server/saved_objects/migrations_730.ts
@@ -16,26 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-// This file should be moved to dashboard/server/
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { SavedObjectsMigrationLogger } from 'src/core/server';
+
import { inspect } from 'util';
-import {
- DashboardDoc730ToLatest,
- DashboardDoc700To720,
-} from '../../../../../../plugins/dashboard/public';
+import { SavedObjectMigrationContext } from 'kibana/server';
+import { DashboardDoc730ToLatest } from '../../common';
import { isDashboardDoc } from './is_dashboard_doc';
import { moveFiltersToQuery } from './move_filters_to_query';
-import { migratePanelsTo730 } from './migrate_to_730_panels';
+import { migratePanelsTo730, DashboardDoc700To720 } from '../../common';
-export function migrations730(
- doc:
- | {
- [key: string]: unknown;
- }
- | DashboardDoc700To720,
- logger: SavedObjectsMigrationLogger
-): DashboardDoc730ToLatest | { [key: string]: unknown } {
+export const migrations730 = (doc: DashboardDoc700To720, { log }: SavedObjectMigrationContext) => {
if (!isDashboardDoc(doc)) {
// NOTE: we should probably throw an error here... but for now following suit and in the
// case of errors, just returning the same document.
@@ -48,7 +37,7 @@ export function migrations730(
moveFiltersToQuery(searchSource)
);
} catch (e) {
- logger.warning(
+ log.warning(
`Exception @ migrations730 while trying to migrate dashboard query filters!\n` +
`${e.stack}\n` +
`dashboard: ${inspect(doc, false, null)}`
@@ -75,7 +64,7 @@ export function migrations730(
delete doc.attributes.uiStateJSON;
} catch (e) {
- logger.warning(
+ log.warning(
`Exception @ migrations730 while trying to migrate dashboard panels!\n` +
`Error: ${e.stack}\n` +
`dashboard: ${inspect(doc, false, null)}`
@@ -84,4 +73,4 @@ export function migrations730(
}
return doc as DashboardDoc730ToLatest;
-}
+};
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts
similarity index 96%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts
rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts
index 621983b1ca8a5..a06f64e0f0c40 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts
+++ b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts
@@ -17,8 +17,8 @@
* under the License.
*/
+import { esFilters, Filter } from 'src/plugins/data/public';
import { moveFiltersToQuery, Pre600FilterQuery } from './move_filters_to_query';
-import { esFilters, Filter } from '../../../../../../plugins/data/public';
const filter: Filter = {
meta: { disabled: false, negate: false, alias: '' },
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts
rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts
diff --git a/src/plugins/dashboard/server/types.ts b/src/plugins/dashboard/server/types.ts
new file mode 100644
index 0000000000000..1151b06dbdab7
--- /dev/null
+++ b/src/plugins/dashboard/server/types.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface DashboardPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface DashboardPluginStart {}
diff --git a/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js b/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js
index f745f01873bae..fc6b706f6e01e 100644
--- a/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js
+++ b/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js
@@ -222,9 +222,8 @@ module.exports = (function() {
if (sequence === 'true') return buildLiteralNode(true);
if (sequence === 'false') return buildLiteralNode(false);
if (chars.includes(wildcardSymbol)) return buildWildcardNode(sequence);
- const number = Number(sequence);
- const value = isNaN(number) ? sequence : number;
- return buildLiteralNode(value);
+ const isNumberPattern = /^(-?[1-9]+\d*([.]\d+)?)$|^(-?0[.]\d*[1-9]+)$|^0$|^0.0$|^[.]\d{1,}$/
+ return buildLiteralNode(isNumberPattern.test(sequence) ? Number(sequence) : sequence);
},
peg$c50 = { type: "any", description: "any character" },
peg$c51 = "*",
@@ -3164,4 +3163,4 @@ module.exports = (function() {
SyntaxError: peg$SyntaxError,
parse: peg$parse
};
-})();
\ No newline at end of file
+})();
diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts
index e441420760475..6a69d52d72134 100644
--- a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts
+++ b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts
@@ -278,6 +278,33 @@ describe('kuery AST API', () => {
expect(fromLiteralExpression('true')).toEqual(booleanTrueLiteral);
expect(fromLiteralExpression('false')).toEqual(booleanFalseLiteral);
expect(fromLiteralExpression('42')).toEqual(numberLiteral);
+
+ expect(fromLiteralExpression('.3').value).toEqual(0.3);
+ expect(fromLiteralExpression('.36').value).toEqual(0.36);
+ expect(fromLiteralExpression('.00001').value).toEqual(0.00001);
+ expect(fromLiteralExpression('3').value).toEqual(3);
+ expect(fromLiteralExpression('-4').value).toEqual(-4);
+ expect(fromLiteralExpression('0').value).toEqual(0);
+ expect(fromLiteralExpression('0.0').value).toEqual(0);
+ expect(fromLiteralExpression('2.0').value).toEqual(2.0);
+ expect(fromLiteralExpression('0.8').value).toEqual(0.8);
+ expect(fromLiteralExpression('790.9').value).toEqual(790.9);
+ expect(fromLiteralExpression('0.0001').value).toEqual(0.0001);
+ expect(fromLiteralExpression('96565646732345').value).toEqual(96565646732345);
+
+ expect(fromLiteralExpression('..4').value).toEqual('..4');
+ expect(fromLiteralExpression('.3text').value).toEqual('.3text');
+ expect(fromLiteralExpression('text').value).toEqual('text');
+ expect(fromLiteralExpression('.').value).toEqual('.');
+ expect(fromLiteralExpression('-').value).toEqual('-');
+ expect(fromLiteralExpression('001').value).toEqual('001');
+ expect(fromLiteralExpression('00.2').value).toEqual('00.2');
+ expect(fromLiteralExpression('0.0.1').value).toEqual('0.0.1');
+ expect(fromLiteralExpression('3.').value).toEqual('3.');
+ expect(fromLiteralExpression('--4').value).toEqual('--4');
+ expect(fromLiteralExpression('-.4').value).toEqual('-.4');
+ expect(fromLiteralExpression('-0').value).toEqual('-0');
+ expect(fromLiteralExpression('00949').value).toEqual('00949');
});
test('should allow escaping of special characters with a backslash', () => {
diff --git a/src/plugins/data/common/es_query/kuery/ast/kuery.peg b/src/plugins/data/common/es_query/kuery/ast/kuery.peg
index 389b9a82d2c76..625c5069f936a 100644
--- a/src/plugins/data/common/es_query/kuery/ast/kuery.peg
+++ b/src/plugins/data/common/es_query/kuery/ast/kuery.peg
@@ -247,9 +247,8 @@ UnquotedLiteral
if (sequence === 'true') return buildLiteralNode(true);
if (sequence === 'false') return buildLiteralNode(false);
if (chars.includes(wildcardSymbol)) return buildWildcardNode(sequence);
- const number = Number(sequence);
- const value = isNaN(number) ? sequence : number;
- return buildLiteralNode(value);
+ const isNumberPattern = /^(-?[1-9]+\d*([.]\d+)?)$|^(-?0[.]\d*[1-9]+)$|^0$|^0.0$|^[.]\d{1,}$/
+ return buildLiteralNode(isNumberPattern.test(sequence) ? Number(sequence) : sequence);
}
UnquotedCharacter
diff --git a/src/plugins/data/common/field_formats/constants/base_formatters.ts b/src/plugins/data/common/field_formats/constants/base_formatters.ts
index 6befe8cea71f5..921c50571f727 100644
--- a/src/plugins/data/common/field_formats/constants/base_formatters.ts
+++ b/src/plugins/data/common/field_formats/constants/base_formatters.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { IFieldFormatType } from '../types';
+import { FieldFormatInstanceType } from '../types';
import {
BoolFormat,
@@ -36,7 +36,7 @@ import {
UrlFormat,
} from '../converters';
-export const baseFormatters: IFieldFormatType[] = [
+export const baseFormatters: FieldFormatInstanceType[] = [
BoolFormat,
BytesFormat,
ColorFormat,
diff --git a/src/plugins/data/common/field_formats/converters/custom.ts b/src/plugins/data/common/field_formats/converters/custom.ts
index a1ce0cf3e7b54..4dd011a7feff3 100644
--- a/src/plugins/data/common/field_formats/converters/custom.ts
+++ b/src/plugins/data/common/field_formats/converters/custom.ts
@@ -18,9 +18,9 @@
*/
import { FieldFormat } from '../field_format';
-import { TextContextTypeConvert, FIELD_FORMAT_IDS, IFieldFormatType } from '../types';
+import { TextContextTypeConvert, FIELD_FORMAT_IDS, FieldFormatInstanceType } from '../types';
-export const createCustomFieldFormat = (convert: TextContextTypeConvert): IFieldFormatType =>
+export const createCustomFieldFormat = (convert: TextContextTypeConvert): FieldFormatInstanceType =>
class CustomFieldFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.CUSTOM;
diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts
index 49baa8c074da8..96d0024dff2a2 100644
--- a/src/plugins/data/common/field_formats/field_format.ts
+++ b/src/plugins/data/common/field_formats/field_format.ts
@@ -22,7 +22,7 @@ import { createCustomFieldFormat } from './converters/custom';
import {
FieldFormatsGetConfigFn,
FieldFormatsContentType,
- IFieldFormatType,
+ FieldFormatInstanceType,
FieldFormatConvert,
FieldFormatConvertFunction,
HtmlContextTypeOptions,
@@ -199,7 +199,7 @@ export abstract class FieldFormat {
};
}
- static from(convertFn: FieldFormatConvertFunction): IFieldFormatType {
+ static from(convertFn: FieldFormatConvertFunction): FieldFormatInstanceType {
return createCustomFieldFormat(convertFn);
}
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.test.ts b/src/plugins/data/common/field_formats/field_formats_registry.test.ts
index 0b32a62744fb1..f04524505a711 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.test.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.test.ts
@@ -18,7 +18,7 @@
*/
import { FieldFormatsRegistry } from './field_formats_registry';
import { BoolFormat, PercentFormat, StringFormat } from './converters';
-import { FieldFormatsGetConfigFn, IFieldFormatType } from './types';
+import { FieldFormatsGetConfigFn, FieldFormatInstanceType } from './types';
import { KBN_FIELD_TYPES } from '../../common';
const getValueOfPrivateField = (instance: any, field: string) => instance[field];
@@ -75,10 +75,10 @@ describe('FieldFormatsRegistry', () => {
test('should register field formats', () => {
fieldFormatsRegistry.register([StringFormat, BoolFormat]);
- const registeredFieldFormatters: Map = getValueOfPrivateField(
- fieldFormatsRegistry,
- 'fieldFormats'
- );
+ const registeredFieldFormatters: Map<
+ string,
+ FieldFormatInstanceType
+ > = getValueOfPrivateField(fieldFormatsRegistry, 'fieldFormats');
expect(registeredFieldFormatters.size).toBe(2);
diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts
index 15b1687e22312..b0a57ad6912a7 100644
--- a/src/plugins/data/common/field_formats/field_formats_registry.ts
+++ b/src/plugins/data/common/field_formats/field_formats_registry.ts
@@ -24,7 +24,7 @@ import {
FieldFormatsGetConfigFn,
FieldFormatConfig,
FIELD_FORMAT_IDS,
- IFieldFormatType,
+ FieldFormatInstanceType,
FieldFormatId,
IFieldFormatMetaParams,
IFieldFormat,
@@ -35,7 +35,7 @@ import { SerializedFieldFormat } from '../../../expressions/common/types';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../types';
export class FieldFormatsRegistry {
- protected fieldFormats: Map = new Map();
+ protected fieldFormats: Map = new Map();
protected defaultMap: Record = {};
protected metaParamsOptions: Record = {};
protected getConfig?: FieldFormatsGetConfigFn;
@@ -47,7 +47,7 @@ export class FieldFormatsRegistry {
init(
getConfig: FieldFormatsGetConfigFn,
metaParamsOptions: Record = {},
- defaultFieldConverters: IFieldFormatType[] = baseFormatters
+ defaultFieldConverters: FieldFormatInstanceType[] = baseFormatters
) {
const defaultTypeMap = getConfig('format:defaultTypeMap');
this.register(defaultFieldConverters);
@@ -79,23 +79,23 @@ export class FieldFormatsRegistry {
* Get a derived FieldFormat class by its id.
*
* @param {FieldFormatId} formatId - the format id
- * @return {IFieldFormatType | undefined}
+ * @return {FieldFormatInstanceType | undefined}
*/
- getType = (formatId: FieldFormatId): IFieldFormatType | undefined => {
+ getType = (formatId: FieldFormatId): FieldFormatInstanceType | undefined => {
const fieldFormat = this.fieldFormats.get(formatId);
if (fieldFormat) {
const decoratedFieldFormat: any = this.fieldFormatMetaParamsDecorator(fieldFormat);
if (decoratedFieldFormat) {
- return decoratedFieldFormat as IFieldFormatType;
+ return decoratedFieldFormat as FieldFormatInstanceType;
}
}
return undefined;
};
- getTypeWithoutMetaParams = (formatId: FieldFormatId): IFieldFormatType | undefined => {
+ getTypeWithoutMetaParams = (formatId: FieldFormatId): FieldFormatInstanceType | undefined => {
return this.fieldFormats.get(formatId);
};
@@ -106,12 +106,12 @@ export class FieldFormatsRegistry {
*
* @param {KBN_FIELD_TYPES} fieldType
* @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types
- * @return {IFieldFormatType | undefined}
+ * @return {FieldFormatInstanceType | undefined}
*/
getDefaultType = (
fieldType: KBN_FIELD_TYPES,
esTypes: ES_FIELD_TYPES[]
- ): IFieldFormatType | undefined => {
+ ): FieldFormatInstanceType | undefined => {
const config = this.getDefaultConfig(fieldType, esTypes);
return this.getType(config.id);
@@ -206,14 +206,16 @@ export class FieldFormatsRegistry {
* Get filtered list of field formats by format type
*
* @param {KBN_FIELD_TYPES} fieldType
- * @return {IFieldFormatType[]}
+ * @return {FieldFormatInstanceType[]}
*/
- getByFieldType(fieldType: KBN_FIELD_TYPES): IFieldFormatType[] {
+ getByFieldType(fieldType: KBN_FIELD_TYPES): FieldFormatInstanceType[] {
return [...this.fieldFormats.values()]
- .filter((format: IFieldFormatType) => format && format.fieldType.indexOf(fieldType) !== -1)
+ .filter(
+ (format: FieldFormatInstanceType) => format && format.fieldType.indexOf(fieldType) !== -1
+ )
.map(
- (format: IFieldFormatType) =>
- this.fieldFormatMetaParamsDecorator(format) as IFieldFormatType
+ (format: FieldFormatInstanceType) =>
+ this.fieldFormatMetaParamsDecorator(format) as FieldFormatInstanceType
);
}
@@ -238,7 +240,7 @@ export class FieldFormatsRegistry {
});
}
- register(fieldFormats: IFieldFormatType[]) {
+ register(fieldFormats: FieldFormatInstanceType[]) {
fieldFormats.forEach(fieldFormat => this.fieldFormats.set(fieldFormat.id, fieldFormat));
}
@@ -246,12 +248,12 @@ export class FieldFormatsRegistry {
* FieldFormat decorator - provide a one way to add meta-params for all field formatters
*
* @private
- * @param {IFieldFormatType} fieldFormat - field format type
- * @return {IFieldFormatType | undefined}
+ * @param {FieldFormatInstanceType} fieldFormat - field format type
+ * @return {FieldFormatInstanceType | undefined}
*/
private fieldFormatMetaParamsDecorator = (
- fieldFormat: IFieldFormatType
- ): IFieldFormatType | undefined => {
+ fieldFormat: FieldFormatInstanceType
+ ): FieldFormatInstanceType | undefined => {
const getMetaParams = (customParams: Record) => this.buildMetaParams(customParams);
if (fieldFormat) {
diff --git a/src/plugins/data/common/field_formats/index.ts b/src/plugins/data/common/field_formats/index.ts
index 13d3d9d73d43a..b64e115fd55ff 100644
--- a/src/plugins/data/common/field_formats/index.ts
+++ b/src/plugins/data/common/field_formats/index.ts
@@ -52,6 +52,6 @@ export {
FieldFormatConfig,
FieldFormatId,
// Used in data plugin only
- IFieldFormatType,
+ FieldFormatInstanceType,
IFieldFormat,
} from './types';
diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts
index 7c1d6a8522e52..5f11c7fe094bc 100644
--- a/src/plugins/data/common/field_formats/types.ts
+++ b/src/plugins/data/common/field_formats/types.ts
@@ -16,9 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-
import { FieldFormat } from './field_format';
-export { FieldFormat };
/** @public **/
export type FieldFormatsContentType = 'html' | 'text';
@@ -82,10 +80,12 @@ export type IFieldFormat = PublicMethodsOf;
*/
export type FieldFormatId = FIELD_FORMAT_IDS | string;
-export type IFieldFormatType = (new (
+/** @internal **/
+export type FieldFormatInstanceType = (new (
params?: any,
getConfig?: FieldFormatsGetConfigFn
) => FieldFormat) & {
+ // Static properties:
id: FieldFormatId;
title: string;
fieldType: string | string[];
diff --git a/src/plugins/data/public/actions/filters/brush_event.test.ts b/src/plugins/data/public/actions/filters/brush_event.test.ts
deleted file mode 100644
index 60244354f06e4..0000000000000
--- a/src/plugins/data/public/actions/filters/brush_event.test.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import moment from 'moment';
-
-import { onBrushEvent, BrushEvent } from './brush_event';
-
-import { IndexPatternsContract } from '../../../public';
-import { dataPluginMock } from '../../../public/mocks';
-import { setIndexPatterns } from '../../../public/services';
-import { mockDataServices } from '../../../public/search/aggs/test_helpers';
-
-describe('brushEvent', () => {
- const DAY_IN_MS = 24 * 60 * 60 * 1000;
- const JAN_01_2014 = 1388559600000;
- let baseEvent: BrushEvent;
-
- const aggConfigs = [
- {
- params: {
- field: {},
- },
- getIndexPattern: () => ({
- timeFieldName: 'time',
- fields: {
- getByName: () => undefined,
- filter: () => [],
- },
- }),
- },
- ];
-
- beforeEach(() => {
- mockDataServices();
- setIndexPatterns(({
- ...dataPluginMock.createStartContract().indexPatterns,
- get: async () => ({
- id: 'indexPatternId',
- timeFieldName: 'time',
- fields: {
- getByName: () => undefined,
- filter: () => [],
- },
- }),
- } as unknown) as IndexPatternsContract);
-
- baseEvent = {
- data: {
- ordered: {
- date: false,
- },
- series: [
- {
- values: [
- {
- xRaw: {
- column: 0,
- table: {
- columns: [
- {
- id: '1',
- meta: {
- type: 'histogram',
- indexPatternId: 'indexPatternId',
- aggConfigParams: aggConfigs[0].params,
- },
- },
- ],
- },
- },
- },
- ],
- },
- ],
- },
- range: [],
- };
- });
-
- test('should be a function', () => {
- expect(typeof onBrushEvent).toBe('function');
- });
-
- test('ignores event when data.xAxisField not provided', async () => {
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
- });
-
- describe('handles an event when the x-axis field is a date field', () => {
- describe('date field is index pattern timefield', () => {
- beforeEach(() => {
- aggConfigs[0].params.field = {
- name: 'time',
- type: 'date',
- };
- baseEvent.data.ordered = { date: true };
- });
-
- afterAll(() => {
- baseEvent.range = [];
- baseEvent.data.ordered = { date: false };
- });
-
- test('by ignoring the event when range spans zero time', async () => {
- baseEvent.range = [JAN_01_2014, JAN_01_2014];
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
- });
-
- test('by updating the timefilter', async () => {
- baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeDefined();
-
- if (filter) {
- expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
- // Set to a baseline timezone for comparison.
- expect(filter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString());
- }
- });
- });
-
- describe('date field is not index pattern timefield', () => {
- beforeEach(() => {
- aggConfigs[0].params.field = {
- name: 'anotherTimeField',
- type: 'date',
- };
- baseEvent.data.ordered = { date: true };
- });
-
- afterAll(() => {
- baseEvent.range = [];
- baseEvent.data.ordered = { date: false };
- });
-
- test('creates a new range filter', async () => {
- const rangeBegin = JAN_01_2014;
- const rangeEnd = rangeBegin + DAY_IN_MS;
- baseEvent.range = [rangeBegin, rangeEnd];
- const filter = await onBrushEvent(baseEvent);
-
- expect(filter).toBeDefined();
-
- if (filter) {
- expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
- expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
- expect(filter.range.anotherTimeField).toHaveProperty(
- 'format',
- 'strict_date_optional_time'
- );
- }
- });
- });
- });
-
- describe('handles an event when the x-axis field is a number', () => {
- beforeAll(() => {
- aggConfigs[0].params.field = {
- name: 'numberField',
- type: 'number',
- };
- });
-
- afterAll(() => {
- baseEvent.range = [];
- });
-
- test('by ignoring the event when range does not span at least 2 values', async () => {
- baseEvent.range = [1];
- const filter = await onBrushEvent(baseEvent);
- expect(filter).toBeUndefined();
- });
-
- test('by creating a new filter', async () => {
- baseEvent.range = [1, 2, 3, 4];
- const filter = await onBrushEvent(baseEvent);
-
- expect(filter).toBeDefined();
-
- if (filter) {
- expect(filter.range.numberField.gte).toBe(1);
- expect(filter.range.numberField.lt).toBe(4);
- expect(filter.range.numberField).not.toHaveProperty('format');
- }
- });
- });
-});
diff --git a/src/plugins/data/public/actions/filters/brush_event.ts b/src/plugins/data/public/actions/filters/brush_event.ts
deleted file mode 100644
index 714f005fbeb6d..0000000000000
--- a/src/plugins/data/public/actions/filters/brush_event.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { get, last } from 'lodash';
-import moment from 'moment';
-import { esFilters, IFieldType, RangeFilterParams } from '../../../public';
-import { getIndexPatterns } from '../../../public/services';
-import { deserializeAggConfig } from '../../search/expressions/utils';
-
-export interface BrushEvent {
- data: {
- ordered: {
- date: boolean;
- };
- series: Array>;
- };
- range: number[];
-}
-
-export async function onBrushEvent(event: BrushEvent) {
- const isDate = get(event.data, 'ordered.date');
- const xRaw: Record = get(event.data, 'series[0].values[0].xRaw');
-
- if (!xRaw) {
- return;
- }
-
- const column: Record = xRaw.table.columns[xRaw.column];
-
- if (!column || !column.meta) {
- return;
- }
-
- const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId);
- const aggConfig = deserializeAggConfig({
- ...column.meta,
- indexPattern,
- });
- const field: IFieldType = aggConfig.params.field;
-
- if (!field || event.range.length <= 1) {
- return;
- }
-
- const min = event.range[0];
- const max = last(event.range);
-
- if (min === max) {
- return;
- }
-
- const range: RangeFilterParams = {
- gte: isDate ? moment(min).toISOString() : min,
- lt: isDate ? moment(max).toISOString() : max,
- };
-
- if (isDate) {
- range.format = 'strict_date_optional_time';
- }
-
- return esFilters.buildRangeFilter(field, range, indexPattern);
-}
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts
deleted file mode 100644
index 1ed09002816d1..0000000000000
--- a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import {
- fieldFormats,
- FieldFormatsGetConfigFn,
- esFilters,
- IndexPatternsContract,
-} from '../../../public';
-import { dataPluginMock } from '../../../public/mocks';
-import { setIndexPatterns } from '../../../public/services';
-import { mockDataServices } from '../../../public/search/aggs/test_helpers';
-import { createFiltersFromEvent, EventData } from './create_filters_from_event';
-
-const mockField = {
- name: 'bytes',
- indexPattern: {
- id: 'logstash-*',
- },
- filterable: true,
- format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn),
-};
-
-describe('createFiltersFromEvent', () => {
- let dataPoints: EventData[];
-
- beforeEach(() => {
- dataPoints = [
- {
- table: {
- columns: [
- {
- name: 'test',
- id: '1-1',
- meta: {
- type: 'histogram',
- indexPatternId: 'logstash-*',
- aggConfigParams: {
- field: 'bytes',
- interval: 30,
- otherBucket: true,
- },
- },
- },
- ],
- rows: [
- {
- '1-1': '2048',
- },
- ],
- },
- column: 0,
- row: 0,
- value: 'test',
- },
- ];
-
- mockDataServices();
- setIndexPatterns(({
- ...dataPluginMock.createStartContract().indexPatterns,
- get: async () => ({
- id: 'logstash-*',
- fields: {
- getByName: () => mockField,
- filter: () => [mockField],
- },
- }),
- } as unknown) as IndexPatternsContract);
- });
-
- test('ignores event when value for rows is not provided', async () => {
- dataPoints[0].table.rows[0]['1-1'] = null;
- const filters = await createFiltersFromEvent(dataPoints);
-
- expect(filters.length).toEqual(0);
- });
-
- test('handles an event when aggregations type is a terms', async () => {
- if (dataPoints[0].table.columns[0].meta) {
- dataPoints[0].table.columns[0].meta.type = 'terms';
- }
- const filters = await createFiltersFromEvent(dataPoints);
-
- expect(filters.length).toEqual(1);
- expect(filters[0].query.match_phrase.bytes).toEqual('2048');
- });
-
- test('handles an event when aggregations type is not terms', async () => {
- const filters = await createFiltersFromEvent(dataPoints);
-
- expect(filters.length).toEqual(1);
-
- const [rangeFilter] = filters;
-
- if (esFilters.isRangeFilter(rangeFilter)) {
- expect(rangeFilter.range.bytes.gte).toEqual(2048);
- expect(rangeFilter.range.bytes.lt).toEqual(2078);
- }
- });
-});
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.ts b/src/plugins/data/public/actions/filters/create_filters_from_event.ts
deleted file mode 100644
index e62945a592072..0000000000000
--- a/src/plugins/data/public/actions/filters/create_filters_from_event.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { KibanaDatatable } from '../../../../../plugins/expressions/public';
-import { deserializeAggConfig } from '../../search/expressions';
-import { esFilters, Filter } from '../../../public';
-import { getIndexPatterns } from '../../../public/services';
-
-export interface EventData {
- table: Pick;
- column: number;
- row: number;
- value: any;
-}
-
-/**
- * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter
- * terms based on a specific cell in the tabified data.
- *
- * @param {EventData['table']} table - tabified table data
- * @param {number} columnIndex - current column index
- * @param {number} rowIndex - current row index
- * @return {array} - array of terms to filter against
- */
-const getOtherBucketFilterTerms = (
- table: EventData['table'],
- columnIndex: number,
- rowIndex: number
-) => {
- if (rowIndex === -1) {
- return [];
- }
-
- // get only rows where cell value matches current row for all the fields before columnIndex
- const rows = table.rows.filter(row => {
- return table.columns.every((column, i) => {
- return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex;
- });
- });
- const terms: any[] = rows.map(row => row[table.columns[columnIndex].id]);
-
- return [
- ...new Set(
- terms.filter(term => {
- const notOther = term !== '__other__';
- const notMissing = term !== '__missing__';
- return notOther && notMissing;
- })
- ),
- ];
-};
-
-/**
- * Assembles the filters needed to apply filtering against a specific cell value, while accounting
- * for cases like if the value is a terms agg in an `__other__` or `__missing__` bucket.
- *
- * @param {EventData['table']} table - tabified table data
- * @param {number} columnIndex - current column index
- * @param {number} rowIndex - current row index
- * @param {string} cellValue - value of the current cell
- * @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters()
- */
-const createFilter = async (table: EventData['table'], columnIndex: number, rowIndex: number) => {
- if (!table || !table.columns || !table.columns[columnIndex]) {
- return;
- }
- const column = table.columns[columnIndex];
- if (!column.meta || !column.meta.indexPatternId) {
- return;
- }
- const aggConfig = deserializeAggConfig({
- type: column.meta.type,
- aggConfigParams: column.meta.aggConfigParams ? column.meta.aggConfigParams : {},
- indexPattern: await getIndexPatterns().get(column.meta.indexPatternId),
- });
- let filter: Filter[] = [];
- const value: any = rowIndex > -1 ? table.rows[rowIndex][column.id] : null;
- if (value === null || value === undefined || !aggConfig.isFilterable()) {
- return;
- }
- if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
- const terms = getOtherBucketFilterTerms(table, columnIndex, rowIndex);
- filter = aggConfig.createFilter(value, { terms });
- } else {
- filter = aggConfig.createFilter(value);
- }
-
- if (!filter) {
- return;
- }
-
- if (!Array.isArray(filter)) {
- filter = [filter];
- }
-
- return filter;
-};
-
-/** @public */
-export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: boolean) => {
- const filters: Filter[] = [];
-
- await Promise.all(
- dataPoints
- .filter(point => point)
- .map(async val => {
- const { table, column, row } = val;
- const filter: Filter[] = (await createFilter(table, column, row)) || [];
- if (filter) {
- filter.forEach(f => {
- if (negate) {
- f = esFilters.toggleFilterNegated(f);
- }
- filters.push(f);
- });
- }
- })
- );
-
- return filters;
-};
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts
new file mode 100644
index 0000000000000..5d21b395b994f
--- /dev/null
+++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts
@@ -0,0 +1,190 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import moment from 'moment';
+
+import { createFiltersFromRangeSelectAction } from './create_filters_from_range_select';
+
+import { IndexPatternsContract, RangeFilter } from '../../../public';
+import { dataPluginMock } from '../../../public/mocks';
+import { setIndexPatterns } from '../../../public/services';
+import { mockDataServices } from '../../../public/search/aggs/test_helpers';
+import { TriggerContextMapping } from '../../../../ui_actions/public';
+
+describe('brushEvent', () => {
+ const DAY_IN_MS = 24 * 60 * 60 * 1000;
+ const JAN_01_2014 = 1388559600000;
+ let baseEvent: TriggerContextMapping['SELECT_RANGE_TRIGGER']['data'];
+
+ const indexPattern = {
+ id: 'indexPatternId',
+ timeFieldName: 'time',
+ fields: {
+ getByName: () => undefined,
+ filter: () => [],
+ },
+ };
+
+ const aggConfigs = [
+ {
+ params: {
+ field: {},
+ },
+ getIndexPattern: () => indexPattern,
+ },
+ ];
+
+ beforeEach(() => {
+ mockDataServices();
+ setIndexPatterns(({
+ ...dataPluginMock.createStartContract().indexPatterns,
+ get: async () => indexPattern,
+ } as unknown) as IndexPatternsContract);
+
+ baseEvent = {
+ column: 0,
+ table: {
+ type: 'kibana_datatable',
+ columns: [
+ {
+ id: '1',
+ name: '1',
+ meta: {
+ type: 'histogram',
+ indexPatternId: 'indexPatternId',
+ aggConfigParams: aggConfigs[0].params,
+ },
+ },
+ ],
+ rows: [],
+ },
+ range: [],
+ };
+ });
+
+ test('should be a function', () => {
+ expect(typeof createFiltersFromRangeSelectAction).toBe('function');
+ });
+
+ test('ignores event when data.xAxisField not provided', async () => {
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
+ });
+
+ describe('handles an event when the x-axis field is a date field', () => {
+ describe('date field is index pattern timefield', () => {
+ beforeEach(() => {
+ aggConfigs[0].params.field = {
+ name: 'time',
+ type: 'date',
+ };
+ });
+
+ afterAll(() => {
+ baseEvent.range = [];
+ aggConfigs[0].params.field = {};
+ });
+
+ test('by ignoring the event when range spans zero time', async () => {
+ baseEvent.range = [JAN_01_2014, JAN_01_2014];
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
+ });
+
+ test('by updating the timefilter', async () => {
+ baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toBeDefined();
+
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
+ // Set to a baseline timezone for comparison.
+ expect(rangeFilter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString());
+ }
+ });
+ });
+
+ describe('date field is not index pattern timefield', () => {
+ beforeEach(() => {
+ aggConfigs[0].params.field = {
+ name: 'anotherTimeField',
+ type: 'date',
+ };
+ });
+
+ afterAll(() => {
+ baseEvent.range = [];
+ aggConfigs[0].params.field = {};
+ });
+
+ test('creates a new range filter', async () => {
+ const rangeBegin = JAN_01_2014;
+ const rangeEnd = rangeBegin + DAY_IN_MS;
+ baseEvent.range = [rangeBegin, rangeEnd];
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+
+ expect(filter).toBeDefined();
+
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
+ expect(rangeFilter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
+ expect(rangeFilter.range.anotherTimeField).toHaveProperty(
+ 'format',
+ 'strict_date_optional_time'
+ );
+ }
+ });
+ });
+ });
+
+ describe('handles an event when the x-axis field is a number', () => {
+ beforeAll(() => {
+ aggConfigs[0].params.field = {
+ name: 'numberField',
+ type: 'number',
+ };
+ });
+
+ afterAll(() => {
+ baseEvent.range = [];
+ });
+
+ test('by ignoring the event when range does not span at least 2 values', async () => {
+ baseEvent.range = [1];
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+ expect(filter).toEqual([]);
+ });
+
+ test('by creating a new filter', async () => {
+ baseEvent.range = [1, 2, 3, 4];
+ const filter = await createFiltersFromRangeSelectAction(baseEvent);
+
+ expect(filter).toBeDefined();
+
+ if (filter.length) {
+ const rangeFilter = filter[0] as RangeFilter;
+ expect(rangeFilter.range.numberField.gte).toBe(1);
+ expect(rangeFilter.range.numberField.lt).toBe(4);
+ expect(rangeFilter.range.numberField).not.toHaveProperty('format');
+ }
+ });
+ });
+});
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts
new file mode 100644
index 0000000000000..409614ca9c380
--- /dev/null
+++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts
@@ -0,0 +1,64 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { last } from 'lodash';
+import moment from 'moment';
+import { esFilters, IFieldType, RangeFilterParams } from '../../../public';
+import { getIndexPatterns } from '../../../public/services';
+import { deserializeAggConfig } from '../../search/expressions/utils';
+import { RangeSelectTriggerContext } from '../../../../embeddable/public';
+
+export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) {
+ const column: Record = event.table.columns[event.column];
+
+ if (!column || !column.meta) {
+ return [];
+ }
+
+ const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId);
+ const aggConfig = deserializeAggConfig({
+ ...column.meta,
+ indexPattern,
+ });
+ const field: IFieldType = aggConfig.params.field;
+
+ if (!field || event.range.length <= 1) {
+ return [];
+ }
+
+ const min = event.range[0];
+ const max = last(event.range);
+
+ if (min === max) {
+ return [];
+ }
+
+ const isDate = field.type === 'date';
+
+ const range: RangeFilterParams = {
+ gte: isDate ? moment(min).toISOString() : min,
+ lt: isDate ? moment(max).toISOString() : max,
+ };
+
+ if (isDate) {
+ range.format = 'strict_date_optional_time';
+ }
+
+ return esFilters.mapAndFlattenFilters([esFilters.buildRangeFilter(field, range, indexPattern)]);
+}
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts
new file mode 100644
index 0000000000000..a0e285c20d776
--- /dev/null
+++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts
@@ -0,0 +1,117 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ fieldFormats,
+ FieldFormatsGetConfigFn,
+ esFilters,
+ IndexPatternsContract,
+} from '../../../public';
+import { dataPluginMock } from '../../../public/mocks';
+import { setIndexPatterns } from '../../../public/services';
+import { mockDataServices } from '../../../public/search/aggs/test_helpers';
+import { createFiltersFromValueClickAction } from './create_filters_from_value_click';
+import { ValueClickTriggerContext } from '../../../../embeddable/public';
+
+const mockField = {
+ name: 'bytes',
+ indexPattern: {
+ id: 'logstash-*',
+ },
+ filterable: true,
+ format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn),
+};
+
+describe('createFiltersFromValueClick', () => {
+ let dataPoints: ValueClickTriggerContext['data']['data'];
+
+ beforeEach(() => {
+ dataPoints = [
+ {
+ table: {
+ columns: [
+ {
+ name: 'test',
+ id: '1-1',
+ meta: {
+ type: 'histogram',
+ indexPatternId: 'logstash-*',
+ aggConfigParams: {
+ field: 'bytes',
+ interval: 30,
+ otherBucket: true,
+ },
+ },
+ },
+ ],
+ rows: [
+ {
+ '1-1': '2048',
+ },
+ ],
+ },
+ column: 0,
+ row: 0,
+ value: 'test',
+ },
+ ];
+
+ mockDataServices();
+ setIndexPatterns(({
+ ...dataPluginMock.createStartContract().indexPatterns,
+ get: async () => ({
+ id: 'logstash-*',
+ fields: {
+ getByName: () => mockField,
+ filter: () => [mockField],
+ },
+ }),
+ } as unknown) as IndexPatternsContract);
+ });
+
+ test('ignores event when value for rows is not provided', async () => {
+ dataPoints[0].table.rows[0]['1-1'] = null;
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
+
+ expect(filters.length).toEqual(0);
+ });
+
+ test('handles an event when aggregations type is a terms', async () => {
+ if (dataPoints[0].table.columns[0].meta) {
+ dataPoints[0].table.columns[0].meta.type = 'terms';
+ }
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
+
+ expect(filters.length).toEqual(1);
+ expect(filters[0].query.match_phrase.bytes).toEqual('2048');
+ });
+
+ test('handles an event when aggregations type is not terms', async () => {
+ const filters = await createFiltersFromValueClickAction({ data: dataPoints });
+
+ expect(filters.length).toEqual(1);
+
+ const [rangeFilter] = filters;
+
+ if (esFilters.isRangeFilter(rangeFilter)) {
+ expect(rangeFilter.range.bytes.gte).toEqual(2048);
+ expect(rangeFilter.range.bytes.lt).toEqual(2078);
+ }
+ });
+});
diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts
new file mode 100644
index 0000000000000..2b426813a98a4
--- /dev/null
+++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts
@@ -0,0 +1,138 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { KibanaDatatable } from '../../../../../plugins/expressions/public';
+import { deserializeAggConfig } from '../../search/expressions';
+import { esFilters, Filter } from '../../../public';
+import { getIndexPatterns } from '../../../public/services';
+import { ValueClickTriggerContext } from '../../../../embeddable/public';
+
+/**
+ * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter
+ * terms based on a specific cell in the tabified data.
+ *
+ * @param {EventData['table']} table - tabified table data
+ * @param {number} columnIndex - current column index
+ * @param {number} rowIndex - current row index
+ * @return {array} - array of terms to filter against
+ */
+const getOtherBucketFilterTerms = (
+ table: Pick,
+ columnIndex: number,
+ rowIndex: number
+) => {
+ if (rowIndex === -1) {
+ return [];
+ }
+
+ // get only rows where cell value matches current row for all the fields before columnIndex
+ const rows = table.rows.filter(row => {
+ return table.columns.every((column, i) => {
+ return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex;
+ });
+ });
+ const terms: any[] = rows.map(row => row[table.columns[columnIndex].id]);
+
+ return [
+ ...new Set(
+ terms.filter(term => {
+ const notOther = term !== '__other__';
+ const notMissing = term !== '__missing__';
+ return notOther && notMissing;
+ })
+ ),
+ ];
+};
+
+/**
+ * Assembles the filters needed to apply filtering against a specific cell value, while accounting
+ * for cases like if the value is a terms agg in an `__other__` or `__missing__` bucket.
+ *
+ * @param {EventData['table']} table - tabified table data
+ * @param {number} columnIndex - current column index
+ * @param {number} rowIndex - current row index
+ * @param {string} cellValue - value of the current cell
+ * @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters()
+ */
+const createFilter = async (
+ table: Pick,
+ columnIndex: number,
+ rowIndex: number
+) => {
+ if (!table || !table.columns || !table.columns[columnIndex]) {
+ return;
+ }
+ const column = table.columns[columnIndex];
+ if (!column.meta || !column.meta.indexPatternId) {
+ return;
+ }
+ const aggConfig = deserializeAggConfig({
+ type: column.meta.type,
+ aggConfigParams: column.meta.aggConfigParams ? column.meta.aggConfigParams : {},
+ indexPattern: await getIndexPatterns().get(column.meta.indexPatternId),
+ });
+ let filter: Filter[] = [];
+ const value: any = rowIndex > -1 ? table.rows[rowIndex][column.id] : null;
+ if (value === null || value === undefined || !aggConfig.isFilterable()) {
+ return;
+ }
+ if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
+ const terms = getOtherBucketFilterTerms(table, columnIndex, rowIndex);
+ filter = aggConfig.createFilter(value, { terms });
+ } else {
+ filter = aggConfig.createFilter(value);
+ }
+
+ if (!filter) {
+ return;
+ }
+
+ if (!Array.isArray(filter)) {
+ filter = [filter];
+ }
+
+ return filter;
+};
+
+/** @public */
+export const createFiltersFromValueClickAction = async ({
+ data,
+ negate,
+}: ValueClickTriggerContext['data']) => {
+ const filters: Filter[] = [];
+
+ await Promise.all(
+ data
+ .filter(point => point)
+ .map(async val => {
+ const { table, column, row } = val;
+ const filter: Filter[] = (await createFilter(table, column, row)) || [];
+ if (filter) {
+ filter.forEach(f => {
+ if (negate) {
+ f = esFilters.toggleFilterNegated(f);
+ }
+ filters.push(f);
+ });
+ }
+ })
+ );
+
+ return esFilters.mapAndFlattenFilters(filters);
+};
diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts
index cdb84ff13f25e..ef9014aafe82d 100644
--- a/src/plugins/data/public/actions/index.ts
+++ b/src/plugins/data/public/actions/index.ts
@@ -18,6 +18,7 @@
*/
export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action';
-export { createFiltersFromEvent } from './filters/create_filters_from_event';
+export { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
+export { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
export { selectRangeAction } from './select_range_action';
export { valueClickAction } from './value_click_action';
diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts
index 6e1f16a09e803..4882e8eafc0d3 100644
--- a/src/plugins/data/public/actions/select_range_action.ts
+++ b/src/plugins/data/public/actions/select_range_action.ts
@@ -23,19 +23,17 @@ import {
IncompatibleActionError,
ActionByType,
} from '../../../../plugins/ui_actions/public';
-import { onBrushEvent } from './filters/brush_event';
+import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
+import { RangeSelectTriggerContext } from '../../../embeddable/public';
import { FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE';
-export interface SelectRangeActionContext {
- data: any;
- timeFieldName: string;
-}
+export type SelectRangeActionContext = RangeSelectTriggerContext;
async function isCompatible(context: SelectRangeActionContext) {
try {
- return Boolean(await onBrushEvent(context.data));
+ return Boolean(await createFiltersFromRangeSelectAction(context.data));
} catch {
return false;
}
@@ -48,6 +46,7 @@ export function selectRangeAction(
return createAction({
type: ACTION_SELECT_RANGE,
id: ACTION_SELECT_RANGE,
+ getIconType: () => 'filter',
getDisplayName: () => {
return i18n.translate('data.filter.applyFilterActionTitle', {
defaultMessage: 'Apply filter to current view',
@@ -59,13 +58,7 @@ export function selectRangeAction(
throw new IncompatibleActionError();
}
- const filter = await onBrushEvent(data);
-
- if (!filter) {
- return;
- }
-
- const selectedFilters = esFilters.mapAndFlattenFilters([filter]);
+ const selectedFilters = await createFiltersFromRangeSelectAction(data);
if (timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts
index 01c32e27da07d..210a58b3f75aa 100644
--- a/src/plugins/data/public/actions/value_click_action.ts
+++ b/src/plugins/data/public/actions/value_click_action.ts
@@ -26,21 +26,17 @@ import {
} from '../../../../plugins/ui_actions/public';
import { getOverlays, getIndexPatterns } from '../services';
import { applyFiltersPopover } from '../ui/apply_filters';
-import { createFiltersFromEvent } from './filters/create_filters_from_event';
+import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
+import { ValueClickTriggerContext } from '../../../embeddable/public';
import { Filter, FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK';
-export interface ValueClickActionContext {
- data: any;
- timeFieldName: string;
-}
+export type ValueClickActionContext = ValueClickTriggerContext;
async function isCompatible(context: ValueClickActionContext) {
try {
- const filters: Filter[] =
- (await createFiltersFromEvent(context.data.data || [context.data], context.data.negate)) ||
- [];
+ const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
return filters.length > 0;
} catch {
return false;
@@ -54,23 +50,23 @@ export function valueClickAction(
return createAction({
type: ACTION_VALUE_CLICK,
id: ACTION_VALUE_CLICK,
+ getIconType: () => 'filter',
getDisplayName: () => {
return i18n.translate('data.filter.applyFilterActionTitle', {
defaultMessage: 'Apply filter to current view',
});
},
isCompatible,
- execute: async ({ timeFieldName, data }: ValueClickActionContext) => {
- if (!(await isCompatible({ timeFieldName, data }))) {
+ execute: async (context: ValueClickActionContext) => {
+ if (!(await isCompatible(context))) {
throw new IncompatibleActionError();
}
- const filters: Filter[] =
- (await createFiltersFromEvent(data.data || [data], data.negate)) || [];
+ const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
- let selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters);
+ let selectedFilters = filters;
- if (selectedFilters.length > 1) {
+ if (filters.length > 1) {
const indexPatterns = await Promise.all(
filters.map(filter => {
return getIndexPatterns().get(filter.meta.index!);
@@ -102,9 +98,9 @@ export function valueClickAction(
selectedFilters = await filterSelectionPromise;
}
- if (timeFieldName) {
+ if (context.timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
- timeFieldName,
+ context.timeFieldName,
selectedFilters
);
filterManager.addFilters(restOfFilters);
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index b62b728beca35..e1e2576b2a0e7 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -293,6 +293,7 @@ import {
convertIPRangeToString,
intervalOptions, // only used in Discover
isDateHistogramBucketAggConfig,
+ isNumberType,
isStringType,
isType,
parentPipelineType,
@@ -364,8 +365,6 @@ export {
SearchResponse,
SearchError,
ISearchSource,
- SearchSource,
- createSearchSource,
SearchSourceFields,
EsQuerySortValue,
SortDirection,
@@ -392,6 +391,7 @@ export const search = {
InvalidEsCalendarIntervalError,
InvalidEsIntervalFormatError,
isDateHistogramBucketAggConfig,
+ isNumberType,
isStringType,
isType,
isValidEsInterval,
diff --git a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx
new file mode 100644
index 0000000000000..6b71739862f62
--- /dev/null
+++ b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx
@@ -0,0 +1,98 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { contains } from 'lodash';
+import React from 'react';
+import { History } from 'history';
+import { i18n } from '@kbn/i18n';
+import { EuiCallOut } from '@elastic/eui';
+import { CoreStart } from 'kibana/public';
+import { toMountPoint } from '../../../../kibana_react/public';
+import { IndexPatternsContract } from './index_patterns';
+
+export type EnsureDefaultIndexPattern = (history: History) => Promise | undefined;
+
+export const createEnsureDefaultIndexPattern = (core: CoreStart) => {
+ let bannerId: string;
+ let timeoutId: NodeJS.Timeout | undefined;
+
+ /**
+ * Checks whether a default index pattern is set and exists and defines
+ * one otherwise.
+ *
+ * If there are no index patterns, redirect to management page and show
+ * banner. In this case the promise returned from this function will never
+ * resolve to wait for the URL change to happen.
+ */
+ return async function ensureDefaultIndexPattern(this: IndexPatternsContract, history: History) {
+ const patterns = await this.getIds();
+ let defaultId = core.uiSettings.get('defaultIndex');
+ let defined = !!defaultId;
+ const exists = contains(patterns, defaultId);
+
+ if (defined && !exists) {
+ core.uiSettings.remove('defaultIndex');
+ defaultId = defined = false;
+ }
+
+ if (defined) {
+ return;
+ }
+
+ // If there is any index pattern created, set the first as default
+ if (patterns.length >= 1) {
+ defaultId = patterns[0];
+ core.uiSettings.set('defaultIndex', defaultId);
+ } else {
+ const canManageIndexPatterns = core.application.capabilities.management.kibana.index_patterns;
+ const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home';
+
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ // Avoid being hostile to new users who don't have an index pattern setup yet
+ // give them a friendly info message instead of a terse error message
+ bannerId = core.overlays.banners.replace(
+ bannerId,
+ toMountPoint(
+
+ )
+ );
+
+ // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
+ timeoutId = setTimeout(() => {
+ core.overlays.banners.remove(bannerId);
+ timeoutId = undefined;
+ }, 15000);
+
+ history.push(redirectTarget);
+
+ // return never-resolving promise to stop resolving and wait for the url change
+ return new Promise(() => {});
+ }
+ };
+};
diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts
index c429431b632bd..cf1f83d0e28cb 100644
--- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts
+++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts
@@ -21,9 +21,9 @@
import { IndexPatternsService } from './index_patterns';
import {
SavedObjectsClientContract,
- IUiSettingsClient,
HttpSetup,
SavedObjectsFindResponsePublic,
+ CoreStart,
} from 'kibana/public';
jest.mock('./index_pattern', () => {
@@ -61,10 +61,10 @@ describe('IndexPatterns', () => {
}) as Promise>
);
- const uiSettings = {} as IUiSettingsClient;
+ const core = {} as CoreStart;
const http = {} as HttpSetup;
- indexPatterns = new IndexPatternsService(uiSettings, savedObjectsClient, http);
+ indexPatterns = new IndexPatternsService(core, savedObjectsClient, http);
});
test('does cache gets for the same id', async () => {
diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts
index acce5ed57683c..b5d66a6aab60a 100644
--- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts
+++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts
@@ -22,11 +22,16 @@ import {
SimpleSavedObject,
IUiSettingsClient,
HttpStart,
+ CoreStart,
} from 'src/core/public';
import { createIndexPatternCache } from './_pattern_cache';
import { IndexPattern } from './index_pattern';
import { IndexPatternsApiClient, GetFieldsOptions } from './index_patterns_api_client';
+import {
+ createEnsureDefaultIndexPattern,
+ EnsureDefaultIndexPattern,
+} from './ensure_default_index_pattern';
const indexPatternCache = createIndexPatternCache();
@@ -37,15 +42,13 @@ export class IndexPatternsService {
private savedObjectsClient: SavedObjectsClientContract;
private savedObjectsCache?: Array>> | null;
private apiClient: IndexPatternsApiClient;
+ ensureDefaultIndexPattern: EnsureDefaultIndexPattern;
- constructor(
- config: IUiSettingsClient,
- savedObjectsClient: SavedObjectsClientContract,
- http: HttpStart
- ) {
+ constructor(core: CoreStart, savedObjectsClient: SavedObjectsClientContract, http: HttpStart) {
this.apiClient = new IndexPatternsApiClient(http);
- this.config = config;
+ this.config = core.uiSettings;
this.savedObjectsClient = savedObjectsClient;
+ this.ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(core);
}
private async refreshSavedObjectsCache() {
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index 2d43cae79ac98..ba1df89c41358 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -45,7 +45,8 @@ const createStartContract = (): Start => {
const queryStartMock = queryServiceMock.createStartContract();
return {
actions: {
- createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']),
+ createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
+ createFiltersFromRangeSelectAction: jest.fn(),
},
autocomplete: autocompleteMock,
search: searchStartMock,
@@ -56,6 +57,7 @@ const createStartContract = (): Start => {
SearchBar: jest.fn(),
},
indexPatterns: ({
+ ensureDefaultIndexPattern: jest.fn(),
make: () => ({
fieldsFetcher: {
fetchForWildcard: jest.fn(),
@@ -67,7 +69,7 @@ const createStartContract = (): Start => {
};
};
-export { searchSourceMock } from './search/mocks';
+export { createSearchSourceMock } from './search/mocks';
export { getCalculateAutoTimeExpression } from './search/aggs';
export const dataPluginMock = {
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 1723545b32522..f3a88287313a0 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -24,13 +24,13 @@ import {
Plugin,
PackageInfo,
} from 'src/core/public';
-import { Storage, IStorageWrapper } from '../../kibana_utils/public';
+import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public';
import {
DataPublicPluginSetup,
DataPublicPluginStart,
DataSetupDependencies,
DataStartDependencies,
- GetInternalStartServicesFn,
+ InternalStartServices,
} from './types';
import { AutocompleteService } from './autocomplete';
import { SearchService } from './search/search_service';
@@ -48,8 +48,6 @@ import {
setQueryService,
setSearchService,
setUiSettings,
- getFieldFormats,
- getNotifications,
} from './services';
import { createSearchBar } from './ui/search_bar/create_search_bar';
import { esaggs } from './search/expressions';
@@ -58,7 +56,12 @@ import {
VALUE_CLICK_TRIGGER,
APPLY_FILTER_TRIGGER,
} from '../../ui_actions/public';
-import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, createFiltersFromEvent } from './actions';
+import {
+ ACTION_GLOBAL_APPLY_FILTER,
+ createFilterAction,
+ createFiltersFromValueClickAction,
+ createFiltersFromRangeSelectAction,
+} from './actions';
import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action';
import {
selectRangeAction,
@@ -99,15 +102,21 @@ export class DataPublicPlugin implements Plugin {
+ const { core: coreStart, self }: any = startServices();
+ return {
+ fieldFormats: self.fieldFormats,
+ notifications: coreStart.notifications,
+ uiSettings: coreStart.uiSettings,
+ searchService: self.search,
+ injectedMetadata: coreStart.injectedMetadata,
+ };
+ };
expressions.registerFunction(esaggs);
- const getInternalStartServices: GetInternalStartServicesFn = () => ({
- fieldFormats: getFieldFormats(),
- notifications: getNotifications(),
- });
-
const queryService = this.queryService.setup({
uiSettings: core.uiSettings,
storage: this.storage,
@@ -130,6 +139,7 @@ export class DataPublicPlugin implements Plugin;
- // (undocumented)
- schema?: string;
- // (undocumented)
+export type AggConfigOptions = Assign;
// Warning: (ae-missing-release-tag) "AggGroupNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -112,7 +105,7 @@ export class AggParamType extends Ba
// (undocumented)
allowedAggs: string[];
// (undocumented)
- makeAgg: (agg: TAggConfig, state?: any) => TAggConfig;
+ makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
}
// Warning: (ae-missing-release-tag) "AggTypeFieldFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -145,7 +138,7 @@ export class AggTypeFilters {
// Warning: (ae-missing-release-tag) "baseFormattersPublic" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
-export const baseFormattersPublic: (import("../../common").IFieldFormatType | typeof DateFormat)[];
+export const baseFormattersPublic: (import("../../common").FieldFormatInstanceType | typeof DateFormat)[];
// Warning: (ae-missing-release-tag) "BUCKET_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -210,9 +203,6 @@ export const connectToQueryState: ({ timefilter: { timefil
// @public (undocumented)
export const createSavedQueryService: (savedObjectsClient: Pick) => SavedQueryService;
-// @public
-export const createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise;
-
// Warning: (ae-missing-release-tag) "CustomFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -248,7 +238,8 @@ export interface DataPublicPluginSetup {
export interface DataPublicPluginStart {
// (undocumented)
actions: {
- createFiltersFromEvent: typeof createFiltersFromEvent;
+ createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
+ createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
};
// Warning: (ae-forgotten-export) The symbol "AutocompleteStart" needs to be exported by the entry point index.d.ts
//
@@ -484,7 +475,7 @@ export type FieldFormatId = FIELD_FORMAT_IDS | string;
export const fieldFormats: {
FieldFormat: typeof FieldFormat;
FieldFormatsRegistry: typeof FieldFormatsRegistry;
- serialize: (agg: import("./search").AggConfig) => import("../../expressions/common").SerializedFieldFormat